Инструменты пользователя

Инструменты сайта


препроцессор

Содержание

Введение

Препроцессор - это программа, которая производит некоторые (иногда весьма значительные) манипуляции с первоначальным текстом программы перед тем, как он подвергается компиляции. Будучи дословно переведенным, с английского, слово препроцессор означает предварительный обрабатыватель.

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

  • обработку макроопределений;
  • включение файлов;
  • «рациональную» предобработку;
  • расширение языка.

Например, весьма часто в программах приходится использовать «ничего не говорящие» числа. Это могут быть какие-то математические константы или размеры используемых в программе массивов и т.д. Общепризнано, что обилие таких констант затрудняет понимание программ и считается признаком плохого стиля программирования. В среде программистов такие константы получили язвительное название магических чисел. Чтобы программа не изобиловала ими, языки программирования позволяют дать константе имя и далее использовать его везде вместо самой константы.

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

#define P1 3.14159
#define E  2.71284

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

Препроцессор языка C позволяет переопределять не только константы, но и целиком программные конструкции. Например, можно написать определение:

#define forever for(;;)

и затем всюду писать бесконечные циклы в виде:

forever { <тело цикла> }

А если вам не нравятся фигурные скобки, то определите

#define begin {
#define end }

Подобные определения, называемые макроопределениями (макросами).

Еще одна важная «услуга» препроцессора - включение в исходный текст содержимого других файлов. Эта возможность в основном используется для того, чтобы снабжать программы какими-то общими для всех файлов определениями. Например, чрезвычайно часто в начале программы на языке C встречается препроцессорная конструкция:

#include <iostream>

Когда исходный текст программы обрабатывается препроцессором, на место этой инструкции ставится содержимое файла iostream, содержащего макроопределения и объявления данных, необходимых для работы потоков ввода-вывода.

Оператор (директива) препроцессора - это одна строка исходного текста, начинающаяся с символа #, за которым следуют название оператора (define, pragma, include, if) и операнды. Операторы препроцессора могут появляться в любом месте программы, и их действие распространяется на весь исходный файл.

Определение констант с помощью #define

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

#include <iostream>
using namespace std;
#define NAME "Vasya Pupkin."
void main ()
{
    cout << " My name is " << NAME;
}
Результат работы: 
My name is Vasya Pupkin.

Замены в тексте можно отменять с помощью команды:

#undef <имя>

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

#define M 16
#undef M
#define M 'C'
#undef M
#define M "C"

Директиву #undef удобно использовать при разработке больших программ, когда они собираются из отдельных «кусков текста», написанных в разное время или разными программистами. В этом случае могут встретиться одинаковые обозначения разных объектов. Чтобы не изменять исходных файлов, включаемый текст можно «обрамлять» подходящими директивами #define - #undef и тем самым устранять возможные ошибки. Например:

     A = 10; //Основной текст.
   #define A X
     A = 5; //Включенный текст.
   #undef A
     B = A; //Основной текст.

Если строка_лексем оказывается слишком длинной, то ее можно продолжить в следующей строке текста программы. Для этого в конце продолжаемой строки помещается символ «\». В ходе одной из стадий препроцессорной обработки этот символ вместе с последующим символом конца строки будет удален из программы. Например:

#define STROKA "Multum, non multa - \
    mnogoe, no nemnogo!"
void main ()
{
   cout << STROKA;
}
На экран будет выведено: 
Multum, non multa - mnogoe, no nemnogo!

Условная компиляция

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

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

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

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

Различие между формами команд #if состоит в следующем.

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

#if 5+5=10
       <TEXT>
#endif

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

#define A 10
#ifdef A
       <TEXT>
#endif

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

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

#elif <константное_выражение>

является сокращением конструкции #else#if.

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

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

Примеры:

#if a+b==5
      cout << 5;
   #else
      cout << "Не известное число";
   #endif

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

Другие директивы препроцессора

Cуществует еще несколько дополнительных директив, вот некоторые из них:

1. Для нумерации строк можно использовать директиву:

#line <константа>

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

#include <iostream>
using namespace std;
#line 100               /* установить счетчик строк */
void main()             /* строка 100 */
{                       /* строка 101 */
  cout<<__LINE__;		/* строка 102 */
}

Команда может определять не только номер строки, но и имя файла:

#line <константа> "<имя_файла>"

2. Директива

#error <последовательность_лексем>

приводит к выдаче диагностического сообщения в виде последовательности лексем. Естественно применение директивы #еrror совместно с условными препроцессорными командами. Например, определив некоторую препроцессорную переменную NAME