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

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

Препроцессор С++ — это программа, которая обрабатывает исходный файл .cpp, содержащий директивы # (такие, как #include, #ifndef и #endif), и преобразует его файл исходного кода, который не содержит таких директив. Эти директивы предназначены для выполнения простых операций с текстом исходного файла, например для выполнения условной компиляции, включения файла и разворачивания макроса. Обычно препроцессор автоматически вызывается компилятором, однако в большинстве систем предусмотрена возможность непосредственного его вызова (часто для этого используется опция компилятора —E и /E).

• Директива #include разворачивается в содержимое файла, имя которого указывается в угловых скобках (< >) или в двойных кавычках (" "), в зависимости от расположения заголовочного файла в стандартном каталоге или в каталоге текущего проекта. Имя файла может содержать .. и / (этот символ правильно интерпретируется компиляторами Windows как разделитель каталогов). Например:

#include "../shared/globaldefs.h"

• С помощью директивы #define определяется макрос. Каждое появление в тексте программы имени, расположенном после директивы #define, заменяется определенным для него значением. Например, директива

#define PI 3.14159265359

указывает препроцессору на необходимость замены каждого появления в текущей единице компиляции лексемы PI лексемой 3.14159265359. Для предотвращения конфликтов имен с переменными и классами общей практикой стало назначение макросам имен, состоящих только из прописных букв. Можно определять макрос с аргументами:

#define SQUARE(x) ((x) * (x))

Считается хорошим стилем окружение в теле макроса скобками любых параметров, а также всего тела макроса, что позволяет избегать проблем, связанных с приоритетностью операторов. В конце концов нам нужно, чтобы запись 7 * SQUARE(2 + 3) разворачивалась в 7 * ((2 + 3) * (2 + З)), а не в 7 * 2 + 3 * 2 + 3.

Компиляторы С++ обычно позволяют определять макросы в командной строке, используя опцию —D или /D. Например:

CC -DPI=3.14159265359 -с main.cpp

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

• Макрос можно отменить в любом месте с помощью директивы #undef:

#undef PI

Эту возможность необходимо использовать, если требуется переопределить макрос, поскольку препроцессор не позволяет определять один и тот же макрос дважды. Эту директиву полезно также применять для управления условной компиляцией.

• Отдельные фрагменты программного кода можно обрабатывать или пропускать при помощи директив #if, #elif, #else и #endif в зависимости от конкретных числовых значений макросов. Например:

#define NO_OPTIM 0

#define OPTIM_FOR_SPEED 1

#define OPTIM_FOR_MEMORY 2

#define OPTIMIZATION OPTIM_FOR_MEMORY

#if OPTIMIZATION == OPTIM_FOR_SPEED

typedef int MyInt;

#elif OPTIMIZATION == OPTIM_FOR_MEMORY

typedef short MyInt;

#else

typedef long long MyInt;

#endif

В приведенном выше примере компилятором будет обрабатываться только второе объявление, которое вводит синоним для short. Изменяя определение макроса OPTIMIZATION, мы получим другие программы. Если макрос не определен, он будет иметь значение 0.

Другим оператором условной компиляции является проверка макроса на предмет его определения. Это можно сделать следующим образом, используя оператор defined():

#define OPTIM_FOR_MEMORY

#if defined(OPTIM_FOR_SPEED)

typedef int MyInt;

#elif defined(OPTIM_FOR_MEMORY)

typedef short MyInt;

#else

typedef long long MyInt;

#endif

• Ради удобства препроцессор воспринимает #ifdef X и #ifndef X как синонимы #if defined(X) и #if !defined(X). Для пpeдoтвpaщeния мнoгoкpaтныx включeний заголовочного файла мы окружаем его содержимое следующими директивами:

#ifndef MYHEADERFILE_H

#define MYHEADERFILE_H

#endif

При первом включении заголовочного файла символ MYHEADERFILE_H оказывается неопределенным, поэтому компилятор обрабатывает программный код, заключенный между директивами #ifndef и #endif. При повторном и последующих включениях заголовочного файла символ MYHEADERFILE_H оказывается определенным, поэтому весь блок #ifndef … #endif пропускается.

• Директива #errоr генерирует на этапе компиляции определенное пользователем сообщение об ошибке. Эта директива часто используется в комбинации с директивами условной компиляции для вывода сообщения о возникновении недопустимого условия. Например:

class UniChar

{

public:

#if BYTE_ORDER == BIG_ENDIAN

uchar row;

uchar cell;

#elif BYTE_ORDER == LITTLE_ENDIAN

uchar cell;

uchar row;

#else

#error "BYTE_ORDER must be BIG_ENDIAN or LITTLE_ENDIAN"

#endif

};

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