Стадии и основные команды препроцессорной обработки

Препроцессор языка Си представляет собой программу (подпрограмму), которая используется для обработки исходного файла на нулевой фазе компиляции. Чаще всего препроцессор не отдельная программа, а входит в состав компилятора.

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

Стадии препроцессорной обработки

1. Все системно-зависимые обозначения (например, системно-зависимый индикатор конца строки) перекодируются в стандартные коды.

2. Каждая пара символов «\» и «конец строки» убираются, и тем самым следующая строка присоединяется к исходной.

3. В тексте распознаются директивы препроцессора, а каждый комментарий заменяется пробелом.

4. Выполняются директивы препроцессора и выполняются макроподстановки.

5. ESC -последовательности в строках (символах) заменяются на их эквиваленты (числовые коды).

6. Смежные символьные строки объединяются в одну строку.

С помощью директив препроцессора можно выполнять следующие действия

• заменять идентификаторы заранее подготовленными последовательностями символов;

• включать тексты из файлов;

• исключать из текста программы отдельные его части (условная компиляция);

• заменять обозначения параметризованным текстом (макроподстановки).

Определены следующие препроцессорные директивы: #define #include #undef #if #ifdef #else #endif #elif #line #error # ##

Кроме того, обычно в данной теме рассматривают и указания компилятору, внешне похожие на директивы препроцессора. Например, в Microsoft Visual C++ существует указание компилятору #pragma.

Замены в тексте

Директива позволяет заменить некоторый идентификатор в тексте программы на некоторый текст (строку замещения). Формат директивы:

#define <идентификатор> [<текст>]

Директива может располагаться в любом месте обрабатываемого текста, ее действие от точки размещения до конца текста программы (имеется ввиду, что в текущий файл могут включаться и другие файлы, это все составляет исходный текст программы). Директива определяет идентификатор как препроцессорный (это условие можно далее использовать для условной компиляции). В результате обработки вхождения идентификатора заменяются текстом, окончание которого признак конца строки. Символы пробела в начале и конце текста в подстановке не участвуют. Если текст слишком длинный его можно продолжить на другой строке, использую символ «\».

Идентификатор можно переопределять с помощью другого использования #define или отменить его действие с помощью директивы

#undef <идентификатор>

Директива широко используется в стандартных заголовочных файлах. Например, в файле math.h:

#define M_E   2.71828182845904523536

#define M_LOG2E 1.44269504088896340736

#define M_LOG10E 0.434294481903251827651

#define M_LN2 0.693147180559945309417

#define M_LN10 2.30258509299404568402

#define M_PI  3.14159265358979323846

#define M_PI_2 1.57079632679489661923

#define M_PI_4 0.785398163397448309616

#define M_1_PI 0.318309886183790671538

#define M_2_PI 0.636619772367581343076

#define M_2_SQRTPI 1.12837916709551257390

#define M_SQRT2 1.41421356237309504880

#define M_SQRT1_2 0.707106781186547524401

 

Макроподстановки

Макрос – это средство замены одной последовательности символов другой (замена в тексте простейшая форма макроса).

Большими возможностями обладают макроопределения с параметрами.

Формат макроподстановки с параметрами:

#define <идентификатор>(<список_параметров>) <текст>

Примеры:

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

Вхождение в программу max(x, 10) заменяется на x<10?10:x

#define abs(x) x>=0?x:-x

Вхождение в программу abs(a) заменяется на a>=0?a:-a

Отличие макросов от функций

1. Функции имеют код (за исключением inline-функций) в одном экземпляре, а коды макроса вставляются в программу столько раз, сколько используется макрос, причем подстановка для макроса осуществляется всегда.

2. Функции работают с определенными типами параметров, макрос пригоден для обработки параметров любого типа, допустимых в выражении строки замещения.

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

Ниже представлен пример с использованием макросов. В примере вместо скобок { } используются слова begin/BEGIN и end/END, как в зыке Паскаль, на самом деле в тексте эти слова заменяются на соответствующие скобки. Также представлен пример макроса с параметрами.

#include <stdio.h>

#define begin {

#define end }

#define BEGIN {

#define END }

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

int main(int argc, char* argv[])

{

int A[10];

int i;

int x=10, y=5, z;

for(i=0; i<10; i++) begin

       A[i]=i*i;

       printf("%d ", A[i]);

end

z=max(x, y); // z=x>y?x:y;

printf("z=%d", z);

return 0;

}

 

Включение текстов из файлов

Директива выполняет простое действие: вставляет текст из одного файла в другой файл в заданном месте. Формат директивы:

#include “(путь_к_файлу)_имя_файла”

#include <(путь_к_файлу)_имя_файла>

Когда используются символы “” файл в первую очередь ищется в текущем каталоге, а затем в системных, в которые установлена среда разработки, а когда <>, то файл ищется в первую очередь в системных каталогах, а затем в текущем. Чаще всего используются включения заголовочных файлов, содержащих заголовки функций и другие общие объявления.

 

Условная компиляция

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

Первая форма:

#if <целочисленное_выражение1>

[<текст>]

[#elif < целочисленное_выражение2>

<текст>]

[#elif < целочисленное_выражение3>

<текст>]

…..

[#else

<текст>]

#endif

Целочисленное выражение не должно содержать операцию sizeof, приведение типов и константы, определенные через enum. Директива работает так, если целочисленное выражение после #if истинно (отлично от 0), то на компиляцию включается текст после #if, остальные тексты после #elif и #else на компиляцию не попадают. Если после #if выражение ложно (равно 0), то последовательно проверяются выражения после #elif, когда находится выражение истинное (отличное от 0), то на компиляцию попадает один фрагмент после этого #elif. Если не одно #elif не сработало, все целочисленные выражения ложные, то на компиляцию поступает текст после #else до #endif (если директива #else есть).

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

#include <stdio.h>

#define _M 0

int main(int argc, char* argv[])

{

#if _M

pr3464572intf("1");

#elif 0

prin4363tf("2");

#elif 0

pri35252ntf("3");

#else

printf("else");

#endif

return 0;

}

Вторая форма:

#ifdef <идентификатор>

[<текст>]

[#elif < целочисленное_выражение2>

<текст>]

[#elif < целочисленное_выражение3>

<текст>]

…..

[#else

<текст>]

#endif

В этой форме первое условие считается истинным, если идентификатор после #ifdef до этого объявлен, как препроцессорный в директиве #define, в этом случае на компиляцию поступает текст после #ifdef, если идентификатор до этого не объявлен, как препроцессорный, то далее условия проверяются, как в первой форме.

Третья форма:

#ifndef <идентификатор>

[<текст>]

[#elif < целочисленное_выражение2>

<текст>]

[#elif < целочисленное_выражение3>

<текст>]

…..

[#else

<текст>]

#endif

В отличие от второй формы первое условие считается истинным, если идентификатор после #ifndef до этого не объявлен, как препроцессорный в директиве #define, а ложном в том случае, если идентификатор объявлен, как препроцессорный, в этом случае далее условия проверяются, как в первой форме.

Вместо директив #ifdef и #ifndef можно использовать более старые формы:

#if defined(<идентификатор>)

#if!defined(<идентификатор>)

defined(<идентификатор>) – может использоваться в качестве ограниченного константного выражения, например, после #elif.

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

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

#include <stdio.h>

#define Pechat

 

int main(int argc, char* argv[])

{

int x=10;

int y=167;

#ifdef Pechat

printf("\nx=%d", x);

#endif

     

#ifdef Pechat

printf("\ny=%d", y);

#endif

printf("\nRez=%d", x+y);

return 0;

}

Также заголовочные файлы должны иметь защиту от повторного включения, например, все стандартные заголовочные файлы имеют защиту от повторного включения. Пример структуры файла stdio.h:

#ifndef _INC_STDIO

#define _INC_STDIO

……..

// Основное содержание файла

…….

#endif /* _INC_STDIO */

 


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



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