Препроцесор мови Сі
Національний Відкритий Університет "ІНТУІТ": www.intuit.ru Ніна Калініна, Ніна Костюкова Лекція 11. Препроцесор мови Сі
В інтегроване середовище підготовки програм на Сі або компілятор мови як обов'язковий компонент входить препроцесор . Призначення препроцесора – обробка вихідного тексту програми до її компіляції. Препроцесорна обробка включає кілька стадій, що виконуються послідовно. Конкретна реалізація може об'єднувати кілька стадій, але результат має бути таким, якби вони виконувались у такому порядку:
1. Усі системно-залежні позначення перекодуються у стандартні коди.
2. Кожна пара із символів ' \ ' і "кінець рядка" разом із пробілами між ними забираються, і тим самим наступний рядок вихідного тексту приєднується до рядка, в якому знаходилася ця пара символів.
4. Виконуються директиви препроцесора та проводяться макропідстановки.
5. Ескейп-послідовності у символьних константах та символьних рядках замінюються на їх еквіваленти.
6. Суміжні символьні рядки конкатенуються, тобто з'єднуються в один рядок.
7. Кожна препроцесорна лексема перетворюється на текст мовою Сі.
Пояснимо, що розуміється під препроцесорними лексемами чи лексемами препроцесора. До них відносяться символьні константи , імена включаються файлів, ідентифікатори, знаки операцій, препроцесорні числа, розділові знаки, рядкові константи і будь-які символи, відмінні від пробілу.
Стадія обробки директив препроцесора. За її виконання можливі такі дії: заміна ідентифікаторів заздалегідь підготовленими послідовностями символів; включення до програми текстів із зазначених файлів; виключення із програми окремих частинїї тексту, умовна компіляція;
макропідстановка, тобто заміна позначення параметризованим текстом, який формується препроцесором з урахуванням конкретних аргументів.
Символічні константи: #define
Якщо як перший символ у рядку програми використовується символ # , то цей рядок є командним рядком препроцесора (макропроцесора). Командний рядок препроцесора закінчується символом переведення на новий рядок. Якщо безпосередньо перед кінцем рядка поставити символ зворотної косої риси "\", командний рядок буде продовжено на наступний рядок програми.
Директива #define , подібно до всіх директив препроцесора, починається з символу # в самій лівій позиції. Вона може з'явитися в будь-якому місці вихідного файлу, а визначення має силу від місця появи до кінця файлу. Ми активно використовуємо цю директиву для визначення символічних констант у прикладах програм, однак вона має ширше застосування, що ми покажемо далі.
#define ідентифікатор рядок
Замінює кожне входження ідентифікатора ABC у тексті програми на 100 :
Скасує попереднє визначення для ідентифікатора ABC.
Продовження тексту 1"
/* зворотна коса риса продовжує визначення наступного рядка */ #define FOUR TWO*TWO
#define PX printf("X дорівнює %d.\n", x) #define FMT "X дорівнює %d.\n"
x = FOUR; printf(FMT,x); printf("%s\n",MSG); printf("TWO: MSG\n"); return TWO;
В результаті виконання нашого прикладу матимемо:
X дорівнює 2 X дорівнює 4
Текст 1. Продовження тексту 1 TWO: MSG
Розберемо, що сталося. Оператор
printf("X дорівнює %d.\n",x);
оскільки зроблено повну заміну. Тепер ми бачимо, що макровизначення може представляти будь-який рядок,навіть ціле вираження мовою Сі. Зауважимо, що це константний рядок. PX надрукує лише змінну, названу x .
У наступному рядку виконується таке: x = FOUR;
і на цьому все закінчується. Фактичне множення має місце під час роботи препроцесора і при компіляції, а завжди без винятку під час роботи програми. Препроцесор не виконує обчислень. Він лише дуже точно робить запропоновані підстановки. Зауважимо, що макровизначення може містити інші визначення. Деякі компілятори не підтримують цю властивість вкладення. У наступному рядку
printf("X дорівнює %d.\n",x)
коли FMT замінюється відповідним рядком. Цей підхід може виявитися дуже зручним, якщо є довгий рядок, який ми використовуємо кілька разів. У наступному рядку програми MSG замінюється відповідним рядком. Лапки роблять рядок, що заміщає, константою символьного рядка. Оскільки програма отримує її вміст, цей рядок буде запам'ятовуватися в масиві, що закінчується нуль-символом. Так,
#define HAL 'X' визначає символьну константу, а
#define HAR "X" визначає символьний рядок X\0
Зазвичай препроцесор, зустрічаючи одне з макровизначень у програмі, дуже точно замінює їх еквівалентним рядком заміщення. Якщо цей рядок також містить макровизначення, вони також заміняються. Єдиним винятком при заміні є макровизначення, що знаходиться усередині подвійних лапок. Тому
друкує буквально TWO: MSG замість друку наступного тексту:
Продовження тексту 1"
Якщо потрібно надрукувати цей текст, можна використовувати оператор
тому що тут макровизначення знаходяться поза лапками.
Коли слід використовувати символічні константи? Ймовірно, ми повинні застосовувати їх длябільшості чисел. Якщо число є константою, що використовується у обчисленнях, то символічне ім'я робить ясніше її зміст. Якщо число - розмір масиву, символічне ім'я спрощує зміну вашої програми при роботі з великим масивом. Якщо число є системним кодом, скажімо для символу EOF , символічне уявлення робить програму більш переносимою. Змінюється лише визначення EOF. Мнемонічне значення, легкість зміни, переносимість: все це робить
символічні константи заслуговують на увагу!
Використання аргументів із #define
Щоб уникнути помилок при обчисленні виразів, параметри макровизначення необхідно укладати.
#define ідентифікатор1 (ідентифікатор2, . . .) рядок
#define abs(A) (((A) > 0)?(A) : -(A))
Кожне входження виразу abs(arg) у тексті програми замінюється на
((arg) > 0) ? (arg): -(arg),
причому параметр макровизначення А замінюється на arg.
Символ продовжує макровизначення на другий рядок. Це макровизначення зменшує складність висловлювання, що описує масив об'єднань усередині структури.
Макровизначення з аргументами дуже схоже на функцію, оскільки його аргументи укладені в дужки:
/* макровизначення з аргументами */ #define SQUARE(x) x*x
#define PR(x) printf("x дорівнює %d.\n", x)
PR(SQUARE(++x)); return 0;
Усюди, де в нашій програмі з'являється макровизначення SQUARE (x), воно замінюється на x * x. На відміну від наших колишніх прикладів, при використанні цього макровизначення ми можемо вільно застосовувати символи, відмінні від x . У макровизначенні ' x ' заміщається символом, використаним макровиклику програми. Тому макровизначення SQUARE(2) заміщається на 2*2. Таким чином, xдіє як аргумент. Проте, аргумент макровизначення не працює - так само, як аргумент функції. Ось результати виконання програми:
z дорівнює 16. z дорівнює 4.
SQUARE(x) дорівнює 16. SQUARE(x+2) дорівнює 14. 100/SQUARE(2) дорівнює 100. SQUARE(++x) дорівнює 30.
Перші два рядки очевидні. Зауважимо, що навіть усередині подвійних лапок у визначенні PR змінна заміщується відповідним аргументом. Усі аргументи у цьому визначенні заміщуються. Розглянемо третій рядок:
Вона стає наступним рядком:
printf("SQUARE(x) одно %d.\n", SQUARE(x));
після першого етапу макророзширення. Друге SQUARE(x) розширюється, перетворюючись на x*x , а перше залишається без зміни, тому що тепер воно знаходиться всередині лапок в операторі програми, і таким чином захищене від подальшого розширення. Остаточно рядок програми містить
printf("SQUARE(x) одно %d.\n",x*x);
і виводить на друк
SQUARE(x) і x*x.
Якщо макровизначення включає аргумент із подвійними лапками, то аргумент заміщатиметься рядком з макровиклику. Але після цього він надалі не розширюється, навіть якщо рядок є ще одним макровизначенням. У прикладі змінна x стала макровизначенням SQUARE(x) і залишилася ним. Згадаймо, що x=4 . Це дозволяє припустити, що SQUARE(x+2) дорівнюватиме 6*6 або 36 . Але надрукований результат свідчить, що виходить число 14 . Причина такого результату така: препроцесор робить обчислень. Він лише замінює рядок. Усюди, де наше визначення вказує на x, препроцесор підставить рядок x+2.
x*x стає x+2*x+2
Якщо x дорівнює 4 то виходить
Виклик функції передає значення аргументу на функцію під час виконання програми. Макровизов передає рядокаргументів у програмі до її компіляції.
Макровизначення чи функція?
! Багато завдань можна вирішувати, використовуючи макровизначення з аргументами чи функцією. Що з них слід застосовувати? Щодо цього немає строгих правил, але є деякі міркування.
Макровизначення повинні використовуватися як хитрощі, а не як звичайні функції. Вони можуть мати небажані побічні ефекти. Деякі компілятори обмежують макровизначення одним рядком, і, мабуть, краще дотримуватися такого обмеження, навіть якщо ваш компілятор цього не робить.
Перевага макровизначень у тому, що з їх використанні нам потрібно турбуватися про типах змінних, т.к. макровизначення мають справу з символьними рядками, а чи не з фактичними значеннями. Так наше макровизначення SQUARE(x) можна використовувати однаково добре зі змінними типу int або float.
1. У макровизначенні немає прогалин, але вони можуть з'явитися в рядку, що заміщає. Препроцесор вважає, що макровизначення закінчується на першому пробілі, тому все, що стоїть після пробілу, залишається в рядку, що заміщає.
2. Використовуйте круглі дужки для кожного аргументу та всього визначення. Це є гарантією того, що елементи будуть згруповані належним чином у виразі.
3. Для імен макрофункцій слід використовувати великі літери. Ця угода не поширюється так широко, як угода про використання великих літер для макроконстант. Їх застосування застереже від можливих побічних ефектів