Директивы препроцессора. Использование директивы #include для подключения header-файлов.

Директивы препроцессора (прекомпилера).

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

#include

Директива #include производит прямую текстовую подстановку содержимого какого-либо файла непосредственно в то место модуля, где она была применена. Существует 2 формы этой директивы:

#include <my.h> - в этом случае подставляемый файл берётся из каталога для хранения хэдер-файлов стандартных библиотек (путь к нему прописывается в параметрах транслятора). Если файл отсутствует в этом каталоге, то препроцессор выдаёт ошибку.

#include "my.h" - в этом случае подставляемый файл берётся из текущего каталога. Если файл отсутствует в этом каталоге, то он берётся из каталога для хранения хэдер-файлов стандартных библиотек. Если же файл отсутствует и в этом каталоге, то препроцессор выдаёт ошибку.

Допускается применение относительных и абсолютных путей к файлу (последнее нежелательно, т.к. в этом случае проект становится труднопереносимым на другую машину):

#include "..\UDPSEND\TPASocket.h"

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

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

#define и #undef

Директива #define позволяет связать идентификатор (мы будем называть этот идентификатор замещаемой частью) с лексемой (возможно, что пустой), которую называют строкой замещения или замещающей частью директивы #define.

Например,

#define PI 3.14159

При выполнении этой директивы препроцессор заменит в оставшейся части программы все отдельно стоящие вхождения идентификатора PI на соответствующую лексему, в нашем примере – на плавающий литерал 3.14159.

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

Например, результатом выполнения программы:

#include "stdio.h"

#define MY_FORMAT "Result = %d"

#define MY_CONST 5

main()

{

printf(MY_FORMAT, MYCONST);

}

будет являться строка "Result = 5". Как видно из этого примера, препроцессор допускает вложения констант, объявленных с помощью директивы #define.

Более сложной формой директивы #define является её форма с применением аргумента, записываемого в скобках непосредственно после замещаемой части:

#define DEF(x) (x*2+5)

С её помощью можно записывать простейшие функции, которые будут не будут компилироваться в виде отдельной функции, а будут подставлены непосредственно в текст программы на месте её вызова. В ряде случаев это позволяет сократить размер программы, объём используемой памяти и повысить быстродействие программы. Например:

#define DEF(x) (x*2+5)

main()

{

int i,j;

j=4;

i=DEF(j);

}

В результате выполнения программы в переменную i запишется значение 13. Заметим, что в настоящее время такой подход используется очень редко, т.к. достигаемая этим приёмом экономия памяти ничтожно мала по сравнению с типичным объёмом памяти компьютеров.

В директиве #define замещающая часть может отсутствовать:

#define MY_FLAG

Такая форма обычно используется в связке с другими директивами препроцессора.

Отменить действие директивы #define можно с помощью директивы #undef. После выполнения этой директивы, дальнейшие замены констант в программе проводиться не будут.

#define PI 3.14 + 0.00159

float pi1 = PI;

#undef PI

#define PI 3.14159

float pi2 = PI;

#ifdef и #ifndef

Директивы #ifdef, #else, #ifndef, #endif являются директивами условной компиляции. Их работа очень похожа на работу оператора if за исключением того, что эти директивы выполняются на стадии предварительной обработки программы перед компиляцией, а не в ходе выполнения программы.

Директива #ifdef сохраняет участок кода, заключённый между ней и директивами #endif или #else, если идентификатор, стоящий после #ifdef был предварительно определён с помощью директивы #define. В противном случае, этот участок кода будет удалён. Например:

#define INTEGERS

#ifdef INTEGERS

int i,j;

#endif

В этой программе фрагмент кода между #ifdef и #endif будет сохранён, т.к. идентификатор INTEGERS был объявлен. Если же его объявление удалить, или отменить с помощью директивы #undef, то фрагмент кода int i,j будет удалён препроцессором по причине отсутствия объявления идентификатора INTEGERS.

Более расширенная конструкция выглядит так:

#define INTEGERS

#ifdef INTEGERS

int i,j;

#else

double i,j;

#endif

В этом случае, если идентификатор объявлен, то сохраняется код на участке от #ifdef до #else, иначе сохраняется код от #else до #endif. Таким образом, если идентификатор INTEGERS объявлен, то i и j будут объявлены с типом int, иначе – double.

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

С помощью применения вышеописанных директив можно создавать универсальные программы, которые в зависимости от описанного набора идентификаторов, будут транслироваться из одного и того же исходного кода в совершенно разные бинарные файлы. Зачастую такую методику применяют для мультиплатформенных выражений, учитывая особенности целевых платформ с помощью комбинаций #ifdef #else #endif, например:

#ifdef DOS

#define MyInt short

#else

#define MyInt long

#endif

MyInt i;

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

Наши рекомендации