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

Компиляция программы на Си — многопроходная. На ее нулевой фазе для обработки исходного файла используется препроцессор. Компилятор вызывает препроцессор автоматически, однако последний может быть вызван и независимо. Директивы препроцессора — это инструкции, записанные непосредственно в исходном тексте программы.

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

Можно выделить следующие основные виды директив:

· определение макрокоманд;

· вставка файлов;

· условная компиляция программы.

Директивы препроцессора Си перечислены в табл. 10.1.

Таблица 10.1. Директивы препроцессора Си

#define #else #if #ifndef #line
#elif #endif #ifdef #include #undef
При указании любой директивы первым значащим символом в строке должен быть символ "#".

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

10.1. Определение макрокоманд

Используя конструкцию

#define идентификатор текст

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

#define х х

не приведет к зацикливанию препроцессора. Это же позволяет делать макроопределения "многоступенчатыми" и зависящими от ранее сделанных подстановок. Обычное назначение директивы #define — введение удобных символических имен для различного рода констант и ключевых слов:

#define null 0#define begin {

Существенно то, что макроопределениям можно передавать параметры:

#define max((a),(b)) ((a)>(b)?(a):(b))

Формальные параметры макроопределения должны отличаться друг от друга. Их область действия ограничена определением, в котором они заданы. Список должен быть заключен в круглые скобки. При обращении к директиве с любыми фактическими аргументами они будут подставлены на место формальных параметров a и b. При этом не происходит вызова какой‑либо функции, но выполняется код условной функции, подставленный препроцессором в каждое место вызова макроопределения. Эта подстановка носит чисто текстовый характер. Никаких вычислений или преобразований типа при этом не производится.

В примере с макроопределением max его формальные аргументы для надежности заключены в круглые скобки. Однако это не гарантирует отсутствия побочных эффектов. Так, при макровызове max(i, a[i++]) он преобразуется к виду ((i)>(a[i++])?(i):(a[i++])), что, очевидно, может привести к лишнему вычислению инкремента.

Директива #defineпозволяет "склеивать" лексемы как строки. Для этого достаточно разделить их знаками ##. Препроцессор объединит такие лексемы в одну, например, определение

#define name(i, j) i##j

при вызове name(a,1) образует идентификатор a1.

Одиночный символ #,помещаемый перед аргументом макроопределения, указывает на то, что аргумент должен быть преобразован в символьную строку, то есть, конструкция вида # формальный_параметр будет заменена на конструкцию " фактический_параметр". Например, сделав "отладочное" макроопределение debug

#define debug(a) printf (#a "=%d\n",a)

мы можем печатать значения переменных в формате имя = значение. Фрагмент программы

int i1=10; debug (i1);

после обработки препроцессором превратится в

int i1=10; printf("i1" " = %d\n", i1);

Наконец, возможно определение вида#define someПри этом все экземпляры идентификатора some будут удалены из текста программы. Сам идентификатор some считается определенным и дает значение 1 при проверке директивой #if.

Директива #undef идентификатор отменяет текущее определение идентификатора. Только когда определение отменено, именованной константе может быть сопоставлено другое значение. Однако многократное повторение определения с одним и тем же значением не считается ошибкой.

10.2. Включение файлов

Общий вид директивы записывается в одном из двух форматов:

#include "имя_пути"

#include <имя_пути>

Директива включает содержимое исходного файла, для которого задано имя_пути, в текущий компилируемый файл. Например, общие для нескольких файлов проекта макроопределения и описания именованных констант могут быть собраны в одном файле и включены директивой #include во все исходные файлы. Включаемые файлы используются также для хранения объявлений внешних переменных и абстрактных типов данных. Препроцессор обрабатывает включенный файл так, как если бы он целиком входил в состав исходного файла в точке вставки директивы.

Директива #include может быть вложенной, то есть, встретиться в файле, включенном другой директивой #include. Препроцессор использует понятие стандартных каталогов для поиска включаемых файлов. Стандартные каталоги в DOS и Windows задаются командой path операционной системы.Угловые скобки сообщают препроцессору, что файл ищется в каталоге, указанном в командной строке компиляции, а затем в стандартных каталогах (для их настройки во многих компиляторах служит опция Include directories):

#include <stdio.h>

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

#include "my.h"


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: