Лекція № 9
Тема: Препроцесор та коментарії
План
Директиви препроцесора
Коментарії
Директиви препроцесора
Директиви препроцесора це інструкції препроцесору С. Предпроцесор C це текстовий процесор, який маніпулює текстом початкового файлу на першій фазі компіляції. Хоча компілятор викликає препроцесор на своїй першій стадії, його можна викликати і окремо для обробки тексту без компіляції.
Директиви препроцесора зазвичай використовуються для полегшення внесення змін в початкові програми і для полегшення їх компіляції в різних середовищах виконання. Розташовані в початковому файлі директиви примушують препроцесор виконувати конкретні дії. Наприклад, препроцесор може замінити лексеми в тексті, вставити вміст інших файлів в початковий файл або подавити компіляцію частини файлу, видаляючи сегменти тексту.
Препроцесор З розпізнає наступні директиви:
#define #if #line
#elif #ifdef #undef
#else #ifndef
#endif #include
Знак номера (#) повинен бути першим нерозділовим символом на рядку, що містить директиву, між символом номера і першою буквою директиви можуть з'являтися розділові символи. Деякі директиви містять аргументи або значення. Будь-який текст, який слідує за директивою (окрім аргументу або значення, який є частиною директиви) повинен бути поміщений в дужки коментаря (/* */).
Директиви препроцесора можуть з'являтися в довільному місці початкового файлу, але вони впливатимуть тільки на частину початкового файлу, в якому вони з'явилися, що залишилася.
Директива #define зазвичай використовується для організації зв'язку змістовних ідентифікаторів з константами, ключовими словами і часто використовуваними операторами і виразами. Представляючі константи ідентифікатори називаються "Оголошеними константами". Представляючі оператори або вирази константи називаються "макросами".
Після визначення ідентифікатора його не можна перевизначити для іншого значення, якщо не видалити первинне визначення. Проте, можна перевизначити ідентифікатор тим же самим визначенням. Отже, одне визначення може з'явитися в програмі кілька разів.
Директива #undef видаляє визначення ідентифікатора. Після видалення визначення ідентифікатор можна перевизначити іншим значенням. Директиви #define і #undef розглядаються відповідно в Розділах 8.2.2 і 8.2.3.
Практично можна виділити два типи макросів. "Об'єктні" макроси не приймають аргументів, а "функціональні" визначені таким чином, що приймають аргументи, і виглядають і діють подібно до викликів функцій. Макроси не генерують дійсні виклики функцій, тому програма працюватиме швидше, якщо замінити виклики функцій макросами. Проте, макроси можуть створити свої проблеми, якщо не підійти до їх визначення і використання зі всією ретельністю. У визначенні макросів з аргументами можливо доведеться скористатися дужками для встановлення належного порядку проведення обчислень у виразах. Крім того, макроси можуть некоректно обробити вирази з побічними ефектами. Додаткову інформацію можна побачити в прикладах Розділу 8.2.2, "Директива #define".
Є три специфічні оператори препроцесора: один представлений знаком номера (#), інший подвоєним знаком номера (##), а третій словом defined. "Рядковий" оператор (#), який передує імені формального параметра макро, примушує препроцесор укласти відповідний дійсний аргумент в рядкові дужки цитат. Оператор "вставки лексем" (##) дозволяє здійснити злиття лексем, заданих як дійсні аргументи, у форму іншої лексеми. Ці два оператори використовуються в контексті директиви #define і описані в Розділах 8.2.2.1 і 8.2.2.2.
І, нарешті, оператора defined спрощує написання складених виразів в деяких директивах макро. Він використовується при умовній компіляції і тому розглянутий в Розділі 8.4.1 "Директиви #if, #elif, #else і #endif".
Директива #define

Синтаксис: #define ідентифікатор текст-замены
#define ідентифікатор(список-параметров)
текст-замены

Директива #define замінює всі появи "ідентифікатор" в початковому файлі на "текст-замены". Ідентифікатор замінюється тільки тоді, коли він формує лексему. (Лексеми описані в розділі "Елементи мови З" і "Короткому огляді синтаксису".) Наприклад, ідентифікатор не буде замінений, якщо він з'являється в рядку або як частина довшого ідентифікатора.
Якщо після ідентифікатора слідує список параметрів, то директива #define замінить кожну появу ідентифікатор(список-параметров) на версію аргументу текст-замены в якій формальні параметри замінені на дійсні аргументи.
Аргумент "текст-замены" складається з ряду лексем, таких як ключові слова, константи або повні оператори. Один або декілька розділових символів повинні відокремлювати текст-замены від ідентифікатора (або від закриваючої дужки списку параметрів). Ці розділові символи не вважаються частиною тексту-заміни, також як і будь-які розділові символи, які слідують за останньою лексемою тексту. Текст, що займає більш за один рядок, може бути продовжений на наступному рядку, якщо до символу переходу на новий рядок помістити знак зворотного ділення (\).
Аргумент текст-замены може бути порожнім. Вибір цієї опції видаляє всі появи ідентифікатора з початкового файлу. Проте, ідентифікатор все ще вважається визначеним і дає значення 1 при його перевірці директивою #if (розглядається в Розділі 8.4.1).
#define stringer(x) printf(#x "\n")

Умовна компіляція
Даний розділ описує синтаксис і використання директив, які управляють "умовною компіляцією". Ці директиви дозволяють подавити компіляцію частини початкового файлу, перевіряючи постійний вираз або ідентифікатор. Результат перевірки визначає, які блоки тексту будуть передані в компілятор і які блоки тексту будуть видалені з початкового файлу при препроцесорній обробці.
Директиви #if, #elif, #else і #endif
#if граничное-постоянное-вираз
[блок-текста]
[#elif граничное-постоянное-вираз
[блок-текста]
[#elif граничное-постоянное-вираз
[блок-текста]

[#else
[блок-текста]
#endif
Директива #if разом з директивами #elif, #else і #endif управляють компіляцією частини початкового файлу. Кожна директива #if в початковому файлі повинна мати відповідну закриваючу директиву #endif. Між директивами #if і #endif може з'явитися будь-яке число директив #elif, але допускається наявність тільки однієї директиви #else. Якщо є директива #else, то вона повинна бути останньою директивою перед #endif.
Препроцесор вибирає одну із заданих появ блоку тексту для подальшої обробки. Цей блок може бути будь-якою послідовністю тексту. Він може займати декілька рядків. Зазвичай блок тексту це текст програми, який має значення для компілятора або препроцесора.
Препроцесор обробляє вибраний блок тексту і передає його компілятору. Якщо блок тексту містить директиви препроцесора, то препроцесор виконає ці директиви.
Всі блоки тексту, не вибрані препроцесором, віддаляються з оброблюваного файлу. Отже, ці блоки тексту не компілюються.
Препроцесор вибирає окремий блок тексту обчислюючи вираз граничної константи, який слідує за кожною директивою #if або #elif, поки результатом виразу граничної константи не буде "істина" (не нуль). Вибирається весь текст (включаючи ті, що починаються з # інші директиви препроцесора) до відповідного #elif, #else або #endif.
Якщо значенням всіх виразів граничних констант буде "брехня", чи ні директиви #elif, то препроцесор вибере блок тексту після пропозиції #else. Якщо пропозиції #else немає, то блок взагалі не вибирається.
Кожен вираз граничної константи відповідає правилам Розділу 5.2.10. Ці вирази не можуть містити виразів sizeof, приведення типа або констант, що перераховують. Проте, вони можуть містити оператора препроцесора defined в спеціальному постійному виразі, що має наступний синтаксис:

defined(ідентифікатор)

Це постійний вираз матиме значення "істина" (не нуль), якщо заданий ідентифікатор визначений, інакше - "брехня" (0). Ідентифікатор, визначений як порожній текст, вважається визначеним.
Директиви #if, #elif, #else і #endif можуть бути вкладені в інші директиви #if. Кожна вкладена директива #else, #elif або #endif належить до найближчої до неї директиви #if.
Приклад 1
У даному прикладі директиви #if і #endif управляють компіляцією одного з трьох викликів функції. Виклик функції credit компілюється, якщо визначений ідентифікатор CREDIT. Якщо визначений ідентифікатор DEBIT, то компілюється виклик функції debit. Якщо не визначений жоден з ідентифікаторів, то компілюється виклик printerror. Звернете увагу на те, що CREDIT і credit це різні ідентифікатори в мові C.
#if defined(CREDIT)
credit();
#elif defined(DEBIT)
debit();
#else
printerror();
#end
Директиви #ifdef і #ifndef
Синтаксис:
#ifdef ідентифікатор
#ifndef ідентифікатор

Директиви #ifdef і #ifndef виконують ті ж функції, що і директива #if з defined(ідентифікатор). Директиви #ifdef і #ifndef можна використовувати скрізь, де допустиме використання #if. Ці директиви реалізовані тільки для забезпечення сумісності з попередніми версіями мови. Переважно використовувати постійний вираз defined(ідентифікатор) з директивою #if.
Коли препроцесор виявляє директиву #ifdef, він перевіряє, чи визначений ідентифікатор. Якщо це так, те значення умови "істина" (не нуль), і "брехня" (0) інакше.
Директива #ifndef перевіряє умову, протилежну умові #ifdef. Якщо ідентифікатор не визначений (або його визначення видалене за допомогою #undef), то умова "істина", і "брехня" (0) інакше.
Коментарії
Коментарій – це текст, який ігнорується при компіляції (інтерпретації)
В мові С є два типи коментаріїв:
- однорядковий;
- багаторядковий.
Однорядковий коментарій створюється за допомогою символів //. Наприклад
int a=5; // a++;
printf("a = %i",a);
На екран буде виведено 5, оскільки текст a++ - коментарій.
Багаторядковий коментар створюється за допомогою комбінації символів /*…*/.