Команды препроцессора

advertisement
Препроцессор языка Си

Препроцессор языка Си предназначен для внесения изменений в
исходный текст программы непосредственно перед ее компиляцией, а
также для установки значений параметров, используемых
компилятором

Обращение к препроцессору осуществляется при помощи набора
директив, начинающихся с символа номера (#), и позволяет добиться
изящества и мобильности Си-программ

В этом разделе рассматриваются некоторые наиболее важные и часто
используемые директивы препроцессора, работающего в составе
компилятора Си
Директива #define

Эта директива, позволяющая присваивать текстовым строкам
символические имена, может иметь один из следующих форматов:
#define identifier <text>
или
#define identifier(parameter-list) <text>

где угловыми скобками (< >) обозначена необязательная часть
конструкций

Первая форма интерпретируется препроцессором
литеральная подстановка, выполняемая перед
обращением к компилятору

Вторая служит для создания макроопределений, семантически
подобных определению обычных функций
как простая
фактическим

Замена выполняется лишь для отдельно стоящих имен и не имеет
места в тех случаях, когда identifier входит в состав текстовых
строк или является составной частью более длинного идентификатора

Список аргументов parameter-list включает в себя один или большее
число формальных параметров, разделенных запятыми и имеющих
уникальные имена. В этом случае препроцессор заменяет имена
параметров, входящие в text, именами соответствующих фактических
аргументов, и лишь после этого будет выполнена реальная
подстановка текста на место идентификатора

Символьная строка text может иметь произвольную длину,
возможно даже превосходящую длину одной строки экрана

Для формирования длинного текста необходимо набрать символ \,
нажать клавишу Enter и продолжить ввод символов с начала новой
экранной строки

С другой стороны, параметр text может вообще отсутствовать в
составе директивы, что соответствует определению идентификатора
identifier без присвоения ему какого-либо конкретного значения

Приведем несколько примеров использования директивы #define
для задания имен текстовых цепочек и формирования
макроопределений функций

1. Наиболее характерным применением этой директивы является
назначение
символических
имен
констант:
#define WIDTH 100
#define LENGTH (WIDTH + 30)

Здесь идентификатор WIDTH объявлен числовой константой, равной
100, а имя LENGTH определено при помощи WIDTH и целого числа
30, причем круглые скобки во втором случае являются
существенными

2. Программисты, желающие видеть свою программу синтаксически
похожей, например, на исходный текст программы на языке типа
Паскаль или Алгол, могут воспользоваться директивой #define для
замены ключевых слов и специальных символов, принятых в языке
Си:
#define
#define
#define
#define
#define

begin {
end ;}
integer int
real float
then
В этом примере последняя инструкция определяет идентификатор
then, не присваивая ему никакого значения. Эффектом такого
определения является простое извлечение из текста программы всех
вхождений имени then, что позволяет записать условный оператор в
таком виде
if (a >= b) then begin max = a; min = b; end

не нарушая тем самым синтаксических правил языка Си

3. Вместо определения функции abs(x) , вычисляющей абсолютную
величину переменной x, в программе можно иметь семантически
эквивалентное ему макроопределение вида
#define abs(x) ((x) >= 0) ? (x) : (-x)

обращение к которому внешне ничем не будет отличаться от
обращения к обычной функции

Директива #define может встречаться в произвольном месте файла,
содержащего исходный текст Си-программы, однако обычно их
принято помещать в начало этого файла
Директива #undef

Данная директива имеет формат
#undef identifier

и заставляет препроцессор игнорировать все последующие вхождения
определенного ранее в инструкции #define имени identifier

Это позволяет ограничить область исходной программы, в пределах
которой identifier обрабатывается препроцессором и имеет
специальное значение

В следующум примере область действия идентификаторов WIDTH и
ADD ограничена лишь частью исходного файла, в пределах которой
они принимаются во внимание препроцессором языка Си:
#define WIDTH 100
#define ADD(x, y) (x) + (y)
...
#undef WIDTH
#undef ADD ...

Инструкция #undef может быть помещена в произвольное место
файла, содержащего исходный текст программы
Директива #include

Формат этой директивы в общем случае определяется следующей
схемой:
#include "pathname"
или
#include <pathname>

где угловые скобки (<>) являются составной частью конструкции.
Здесь pathname есть правильное имя файла в смысле операционной
системы

Директива #include позволяет включать текст файла с именем
pathname в состав файла, содержащего обращение к этой директиве

Если pathname задает полное имя файла от корневого каталога, то
две приведенные выше формы записи директивы эквивалентны

В том же случае, когда задано относительное имя файла,
использование двойных кавычек ( " " ) заставляет препроцессор
прежде всего искать требуемый файл в текущем каталоге, затем
просматривать каталог, определенный в команде вызова компилятора
и, наконец, в случае необходимости продолжить поиск в стандартном
каталоге

Однако при заключении имени файла в угловые скобки текущий
каталог не просматривается

Препроцессор допускает последовательные вложения инструкций
#include максимально до десяти уровней. Это означает, что всякий
файл, подключаемый с помощью этой директивы, может содержать
внутри себя новое обращение к #include

В составе исходного текста программы директива #include может
размещаться в произвольном месте содержащего ее файла
Директивы #if, #ifdef, #ifndef, #else,
#elif и #endif

Директивы #if, #ifdef, #ifndef, #else, #elif и #endif
используются для условной избирательной компиляции различных
фрагментов программы

Главная идея состоит в том, что если выражение, стоящие после
директив #if, #ifdef и #ifndef, оказывается истинным, то будет
скомпилирован код, расположенный между одной из этих трех
директив и директивой #endif. В противном случае данный код
будет опущен

Директива #endif используется для обозначения конца блока

Директиву #else можно использовать с любой из перечисленных
выше директив для предоставления альтернативного варианта
компиляции

Общая форма записи директивы #if:
#if константное выражение

Если константное_выражение является истинным, будет
скомпилирован код, расположенный непосредственно за этой
директивой

Общая форма записи директивы #ifdef:
#ifdef имя_макроса

Если имя_макроса определено в операторе #define, то будет
скомпилирован блок кода, следующий за оператором #ifdef

С помощью директив #if и #elif и оператора препроцессорной
обработки #defined можно также выяснить, определено ли имя
конкретного макроса директивой препроцессора #define. Для этого
используется следующая общая форма записи:
#if defined имя_макроса
последовательность операторов
#endif

Если
имя_макроса
определено,
последовательность_операторов будет скомпилирована. В
противном случае она будет опущена

Оператор #defined можно также предварить оператором отрицания
(!), и тогда условная компиляция будет выполнена в том случае, если
заданное имя макроса не определено

Например: чтобы застраховаться от повторного включения
заголовочного файла hdr.h, его можно оформить следующим
образом:
#if !defined(HDR)
#define HDR
/* здесь содержимое hdr.h */
#endif

При первом включении файла hdr.h будет определено имя HDR, а
при последующих включениях препроцессор обнаружит, что имя HDR
уже определено, и перескочит сразу на #endif

Этот прием может оказаться полезным, когда нужно избежать
многократного включения одного и того же файла. Если им
пользоваться систематически, то в результате каждый заголовочный
файл будет сам включать заголовочные файлы, от которых он зависит,
освободив от этого занятия пользователя.

Общая форма записи #ifndef имя_макроса:
#ifndef имя_макроса

Если имя_макроса не определено в операторе #define, то будет
скомпилирован блок кода, следующий за оператором #ifndef
#ifndef HDR
#define HDR
/* здесь содержимое hdr.h */
#endif

Общая форма записи директивы #elif:
#elif выражение_константа

Данная директива используется для создания оператора if-else-if

Для обработки сразу нескольких альтернатив можно использовать ряд
директив #elif

Вот пример цепочки проверок имени SYSTEM, позволяющей выбрать
нужный файл для включения:
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h«
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
Директива #line

Директива #line используется для изменения содержимого
псевдопеременных __LINE__ и __FILE__ , которые являются
зарегистрированными идентификаторами. Общая форма записи
директивы:
#line номер "имя_файла"

Здесь номер - это любое положительное число, а имя файла - любой
допустимый идентификатор файла. Значение элемента номер
становится номером текущей исходной строки, а значение элемента
имя_файла - именем исходного файла. Имя файла - элемент
необязательный

Директива #line используется, главным образом, в целях отладки и в
специальных приложениях.
Директива #error

Директива #error дает указание компилятору остановить
компиляцию. Она используется в основном для отладки. Общая форма
записи:
#error сообщение

При встрече директивы #error отображается заданное сообщение и
номер строки
Директива #pragma

Работа директивы #pragma зависит от конкретной реализации
компилятора. Она позволяет выдавать компилятору различные
инструкции. Например, компилятор способен поддерживать
трассировку выполнения программы. Заставить компилятор работать
в режиме трассировки можно именно с помощью оператора
#pragma. Общая форма записи:
#pragma ("директива")

Здесь элемент директива означает задаваемую инструкцию (прагму)

Компилятор Си, в котором данная команда не реализована, ее
игнорирует
Download