Шаг 79.
Директивы условной компиляции

    На этом шаге мы рассмотрим директивы условной компиляции, позволяющие генерировать программный код в зависимости от выполнимости определенных условий.

    Условная компиляция обеспечивается в языке C++ набором команд, которые, по существу, управляют не компиляцией, а препроцессорной обработкой:

     #if <константное_выражение>
     #ifdef <идентификатор>
     #ifndef <идентификатор>
     #else
     #endif
     #elif

    Первые три команды выполняют проверку условий, две следующие - позволяют определить диапазон действия проверяемого условия. Последняя команда используется для организации проверки серии условий.

    Общая структура применения директив условной компиляции такова:

     #if/#ifdef/#ifndef <константное_выражение или идентификатор>
          <текст_1>
     #else //необязательная директива
          <текст_2>
     #endif

    Конструкция #else <текст_2> не обязательна. Текст_1 включается в компилируемый текст только при истинности проверяемого условия. Если условие ложно, то при наличии директивы #else на компиляцию передается текст_2. Если директива #else отсутствует, то весь текст от #if до #endif при ложном условии опускается. Различие между формами команд #if состоит в следующем.

    В первой из перечисленных директив #if проверяется значение константного целочисленного выражения. Если оно отлично от нуля, то считается, что проверяемое условие истинно. Например, в результате выполнения директив:

     #if 5+12
          <текст_1>
     #endif
текст_1 всегда будет включен в компилируемую программу.

    В директиве #ifdef проверяется, определен ли с помощью команды #define к текущему моменту идентификатор, помещенный после #ifdef. Если идентификатор определен, то текст_1 используется компилятором.

    В директиве #ifndef проверяется обратное условие - истинным считается неопределенность идентификатора, т.е. тот случай, когда идентификатор не был использован в команде #define или его определение было отменено командой #undef.

    Условную компиляцию удобно применять при отладке программ для включения или исключения контрольных печатей. Например,

     #define DE 1 //Определение идентификатора.
     .   .   .   .   .
     #ifdef DE //Проверка определения идентификатора.
          cout << "Отладочная печать";
     #endif

    Таких печатей, появляющихся в программе в зависимости от определенности идентификатора DE может быть несколько и, убрав директиву #define DE 1, сразу же отключаем все отладочные печати.

    Файлы, предназначенные для препроцессорного включения в модули программы, обычно снабжают защитой от повторного включения. Такое повторное включение может произойти, если несколько модулей, в каждом из которых запланировано препроцессорное включение одного и того же файла, объединяются в общий текст программы. Например, такими средствами защиты снабжены все заголовочные файлы (подобные iostream.h) стандартной библиотеки. Схема защиты от повторного включения может быть такой (фрагмент файла с именем filename):

     #ifndef _FILE_NAME //Если константа не определена
         //Включаемый текст файла  filename.
     #define _FILE_NAME //Определение идентификатора.
     #endif

    Здесь _FILE_NAME - зарезервированный для файла filename препроцессорный идентификатор, который не должен встречаться в других текстах программы. При первом включении содержимого этого файла в программу идентификатор _FILE_NAME не определен, поэтому происходит включение этого файла. В конце этого файла происходит определение этого идентификатора с помощью директивы #define. При повторном обращении к тексту этого файла включения не произойдет, так как выполнение директивы #ifndef _FILE_NAME выработает ложный результат.

    Для организации мультиветвлений во время обработки препроцессором исходного текста программы введена директива

     #elif <константное_выражение>
которая является сокращением конструкции #else #if.

    Структура исходного текста с применением этой директивы такова:

     #if <константное_выражение_1>
          <текст_1>
     #elif <константное_выражение_2>
          <текст_2>
     #elif <константное_выражение_3>
          <текст_3>
     .   .   .   .
     #else
          <текст_N>
     #endif

    Препроцессор проверяет вначале условие в директиве #if, если оно ложно (равно 0) - вычисляет константное_выражение_2, если оноравно О - вычисляется константное_выражение_3 и т.д. Если все выражения ложны, то в компилируемый текст включается текст для случая #else. В противном случае, т.е. при появлении хотя бы одного истинного выражения (в #if или в #elif), начинает обрабатываться текст, расположенный непосредственно за этой директивой, а все остальные директивы не рассматриваются. Таким образом, препроцессор обрабатывает всегда только один из участков текста, выделенных командами условной компиляции.

    В заключение рассмотрим несколько примеров.


    Пример 1.Простая директива условного включения.
   #ifdef ArrFlg
      int Arr[30];
   #endif

    Если во время интерпретации директивы определено макроопределение ArrFlg, то приведенная запись дает генерацию выражения

   int Arr[30];
В противном случае не будет генерировано ни одно выражение.


    Пример 2.
#include <iostream.h>
#define ArrFlg 1
void main ()
{
   #ifdef ArrFlg
       int Arr[30];
   #else
       cout << "Массив не определен!";
   #endif
}
Текст этой программы можно взять здесь.


    Пример 3. Директива условного включения с альтернативой
   #if a+b==5
      cout << 5;
   #else
      cout << 13;
   #endif

    Если выражение a+b==5 представляет величину, отличную от 0, то будет сгенерирована команда cout << 5;, в противном случае будет сгенерирована команда cout << 13;.


    Пример 4. Составная директива условного включения
   #define Alfa 5
   #if Alfa*5>20
      main ()
      #if Alfa==4
         int Arr[2];
      #elif Alfa==3
         char Arr[2];
      #else
         {
      #endif
      #if 0
         printf ("Iza");
      #else
         printf ("Kaja");
      #endif
   #else
      printf ("Ewa");
   #endif
   }
Интерпретация приведенной записи приведет к генерации
   main ()
   {
      printf ("Kaja");
   }


    На следующем шаге мы рассмотрим дополнительные возможности препроцессора.


Предыдущий шаг Содержание Следующий шаг