logo search
ООП для Заоч / Пинчук Лозовская Программир на С

11.3. Директиви препроцесора

Директиви препроцесора використовують для керування процесом компіляції та попередньої модифікації тексту програми. Наведемо ті директиви, які визначаються стандартом мови С++.

Наступні директиви називають директивами умовної компіляції:

#if

#ifdef

#else

#elif

#endif

Вони дозволяють або не дозволяють виконувати компіляцію певних частин програми залежно від виконання умов. Такі директиви застосовуються, якщо треба отримати декілька версій виконуваного коду програми.

Директива

#include

дозволяє включити текст з іншого файлу у текст поточної програми. Наприклад, такий рядок

#include "dalmat.h"

призведе до включення файлу "dalmat.h" у текст програми замість рядка самої директиви. Файл "dalmat.h" буде шукатися спочатку у поточній папці. Якщо там його не буде знайдено, пошук буде здійснюватися у системних папках, шляхи до яких прописані у оточенні процесору (у тому числі у каталозі \INCLUDE ). Ім’я файлу можна писати також у кутових дужках:

#include <dalmat.h>

У такому разі пошук файлу для заміни буде вестись одразу у системних папках.

Директива

#define

дозволяє створювати макроси. Розрізняють три види макросів:

- макроси - ідентифікатори;

- макроси заміни;

- макрос-функції.

Макроси-ідентифікатори мають найпростішу конструкцію:

#define ім’я

Застосовуються вони для умовної компіляції.

Макрос заміни записується таким чином:

#define ім’я послідовність_символів

Такий макрос призводить до заміни в тексті програми всіх входжень послідовності символів ім’я на вказану послідовність_символів.

Макрос-функція теж призводить до певних замін, але вони виконуються по іншим правилам. Розглянемо такий приклад:

#define MIN(A,B) ((A)<(B)) ? (A) : (B)

Тепер кожен раз, коли у програмі буде зустрічатися MIN(A,B), замість нього буде підставлено вираз ((A)<(B)) ? (A) : (B). При цьому у якості A,B може бути записано довільну послідовність символів.

Наведемо два приклади більш-менш вдалого використання макросу заміни та макрос-функції. У стандартному пакеті Borland C++ константу  розташовано під ім’ям M_PI . Якщо ми бажаємо використовувати її під більш зручним ім’ям pi , треба у початку програми записати такий макрос:

#define pi M_PI

У такий же спосіб можна замість імені математичної функції обчислення тангенсу tan(x) задіяти більш звичне у математиці ім’я tg(x). Для цього потрібно записати такий макрос:

#define tg(x) tan(x)

У старих технологіях програмування макроси заміни часто використовуються для реалізації у програмі констант, а макрос-функції можуть застосовуватись як звичайні функції. Але тут має сенс застережити: в обох вказаних випадках будуть суттєво послаблені можливості діагностування помилок на етапі компіляції. У зв’язку з цим рекомендується застосовувати інші засоби для реалізації констант і функцій.

Наступна директива

#undef ім’я

відміняє визначення макросу з вказаним ім’ям.

Директива

#line

застосовується для зміни змісту напередвизначених макросів __LINE__ та __FILE__ . Макрос __LINE__ містить у собі номер рядка у тексті програми, а макрос __FILE__ - ім’я вихідного файлу з програмою, яка виконується.

Директива

#error повідомлення

припиняє компіляцію з виведенням повідомлення.

Директива

#pragma ім’я

застосовується для надання певної інструкції компілятору.

Ряд макросів, які є символьними константами, компілятор визначає автоматично. Такі макроси діляться на дві групи: макроси ANSI і додаткові макроси, які генеруються компілятором (наприклад, компілятором Borland C++). До макросів ANSI, зокрема, відносяться такі:

__DATE__ - рядок, що містить дату компіляції у форматі mmm dd yyyy;

__TIME__ - рядок, що містить час компіляції у форматі hh:mm:ss;

__FILE__ - рядок, що містить ім'я поточного файлу в подвійних лапках.

Умовна компіляція фрагменту програми

Розглянемо деякий фрагмент програми (звичайно це є набір функцій). Запишемо його із застосуванням директив #ifdef та #endif у такий спосіб:

#ifdef _MM_

... фрагмент програми ...

#endif

Тепер, якщо у початку програми макрос-ідентифікатор _MM_ визначено (за допомогою директиви #define), то фрагмент програми комплюватися буде, якщо ж ні, то позначений фрагмент програми при компіляції буде проігноровано.

Запобігання повторного включення h-файлу

У зв'язку з тим, що обробка директив включення #include часто призводить до неявних рекурсій, препроцесорна обробка програми може приводити до того, що в модифікований текст програми деякий h-файл буде включено більш, ніж один раз. Останнє призводить до дублювання визначень і, як результат, до помилок компіляції. Для того, щоб уникнути повторного включення h-файлів можна використати директиви умовної компіляції #ifndef і #endif у сполученні з директивою визначення простого макросу #define. Такий h-файл може бути влаштований у такий спосіб:

#ifndef MYFILE_H

#define MYFILE_H

текст h-файлу

#endif

Контроль параметрів компіляції

Borland C++ визначає ряд макросів, що дозволяють контролювати параметри компіляції, наприклад:

__LARGE__ - визначений, якщо встановлено модель пам'яті LARGE;

__FLAT__ - визначений, якщо встановлено режим побудови 32-бітної прикладної програми з моделлю пам'яті FLAT.

Більш детальну інформацію щодо застосування директив можна подивитись у [7].