Общие сведения
В интегрированную среду подготовки программ на Си или в компилятор языка как обязательный компонент входит препроцессор. Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке:
- Все системно-зависимые обозначения перекодируются в стандартные коды.
- Каждая пара из символов ' \ ' и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов.
- В тексте распознаются директивы и лексемы препроцессора, а каждый комментарий заменяется одним символом пустого промежутка.
- Выполняются директивы препроцессора и производятся макроподстановки.
- Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты.
- Смежные символьные строки конкатенируются, то есть соединяются в одну строку.
- Каждая препроцессорная лексема преобразуется в текст на языке Си.
Поясним, что понимается под препроцессорными лексемами или лексемами препроцессора. К ним относятся символьныеконстанты, имена включаемых файлов, идентификаторы, знаки операций, препроцессорные числа, знаки препинания, строковыеконстанты и любые символы, отличные от пробела.
Стадия обработки директив препроцессора. При ее выполнении возможны следующие действия:
- замена идентификаторов заранее подготовленными последовательностями символов;
- включение в программу текстов из указанных файлов;
- исключение из программы отдельных частей ее текста, условная компиляция;
- макроподстановка, то есть замена обозначения параметризованным текстом, формируемым препроцессором с учетом конкретных аргументов.
Символические константы: #define
Если в качестве первого символа в строке программы используется символ #, то эта строка является командной строкойпрепроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой черты " \ ", то командная строка будет продолжена на следующую строку программы.
Директива #define, подобно всем директивам препроцессора, начинается c символа # в самой левой позиции. Она может появиться в любом месте исходного файла, а даваемое определение имеет силу от места появления до конца файла. Мы активно используем эту директиву для определения символических констант в наших примерах программ, однако она имеет более широкое применение, что мы покажем дальше.
Замена идентификаторов
#define идентификатор строка
Пример:
Заменяет каждое вхождение идентификатора ABC в тексте программы на 100:
Пример:
Отменяет предыдущее определение для идентификатора ABC.
Пример:
/* Простые примеры директивы препроцессора */
#define TWO 2 /* можно использовать комментарии*/
#define MSG "Текст 1.\
Продолжение текста 1"
/* обратная косая черта продолжает определение на следующую строку */
#define FOUR TWO*TWO
#define PX printf("X равен %d.\n", x)
#define FMT "X равен %d.\n"
int main( )
{
int x = TWO;
PX;
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.
В следующей строке выполняется следующее:
превращается
превращается в
и на этом все заканчивается. Фактическое умножение имеет место не во время работы препроцессора и не при компиляции, а всегда без исключения при работе программы (Уточнение: это зависит от конкретного компилятора). Препроцессор не выполняет вычислений. Он только очень точно делает предложенные подстановки. Заметим, что макроопределение может включать другие определения. Некоторые компиляторы не поддерживают это свойство вложения. В следующей строке
превращается в
printf("X равно %d.\n",x)
когда FMT заменяется соответствующей строкой. Этот подход может оказаться очень удобным, если есть длинная строка, которую мы используем несколько раз. В следующей строке программы MSG заменяется соответствующей строкой. Кавычки делают замещающую строку константой символьной строки. Поскольку программа получает ее содержимое, эта строка будет запоминаться в массиве, заканчивающемся нуль-символом. Так,
#define HAL 'X' определяет символьную константу, а
#define HAR "X" определяет строковую строку X\0
Обычно препроцессор, встречая одно из макроопределений в программе, очень точно заменяет их эквивалентной строкой замещения. Если эта строка также содержит макроопределения, они тоже замещаются. Единственным исключением при замене является макроопределение, находящееся внутри двойных кавычек. Поэтому
печатает буквально TWO: MSG вместо печати следующего текста:
2: "Текст 1.
Продолжение текста 1"
Если нам нужно напечатать этот текст, можно использовать оператор
printf("%d: %s\n",TWO,MSG);
потому что здесь макроопределения находятся вне кавычек.
Когда следует использовать символические константы? Вероятно, мы должны применять их для большинства чисел. Если число является константой, используемой в вычислениях, то символическое имя делает яснее ее смысл. Если число - размер массива, тосимволическое имя упрощает изменение вашей программы при работе с большим массивом. Если число является системным кодом, скажем для символа EOF, то символическое представление делает программу более переносимой. Изменяется только определениеEOF. Мнемоническое значение, легкость изменения, переносимость: все это делает символические константы заслуживающими внимания!
|