Препроцессорные директивы. Директивы препроцессора и указания компилятору

Faq 01.08.2019
Faq
сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1 ]. Препроцессор имеет тот же смысл, что и буферный процессор .

Директива #define

Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [19.3 ]. В общем виде директива #define выглядит следующим образом (должно быть задано буквами латинского алфавита):

#define имя_макроса последовательность_символов

В определении, как правило, в конце последовательности символов не ставится точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк [19.3 ].

Имена макросов обычно задаются с помощью букв верхнего регистра.

У директивы #define имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами (макроопределениями с параметрами и макросами, напоминающими функции ) [19.3 ]. Ключевым элементом макроса с параметрами являются круглые скобки, внутри которых находятся собственно формальные параметры. Рассмотрим пример макроса с тремя параметрами для определения следующего условия: будет ли остаток от деления случайной функции на правую границу интервала больше, чем половина этого интервала.

Программный код решения примера

#include #include #include #include #define MAX(a,b,c) ((1+rand()%(b)) > ((a)+(b))/2) ? (c):(b) int main (void) { int a, b, c; srand((unsigned) time(NULL)); printf("\n Enter a, b, c: "); scanf_s("%d%d%d", &a, &b, &c); printf("\n MAX(a,b,c): %d\n", MAX(a,b,c)); printf("\n\n ... Press any key: "); _getch(); return 0; }

Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с ) дает следующее преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы [19.3 ]. И еще, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [19.4 ].

Директива #error

Директива #error заставляет компилятор прекратить компиляцию [19.3 ]. Эта директива используется в основном для отладки. В общем виде директива #error выглядит следующим образом:

#error сообщение – об – ошибке

Заданное сообщение об ошибке (сообщение – об – ошибке ) в двойные кавычки не заключается. Когда встречается данная директива, то выводится сообщение об ошибке – возможно, вместе с другой информацией, определяемой компилятором [19.3 ].

Директива #include

Директива #include дает указание компилятору читать еще один исходный файл – в дополнение к тому файлу, в котором находится сама эта директива [19.3 ]. Имя исходного файла (подключаемого файла) должно быть заключено в двойные кавычки или в угловые скобки. Обычно имена стандартных заголовочных файлов заключают в угловые скобки. А использование кавычек обычно приберегается для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или же в угловые скобки. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя файла заключено в угловые скобки [19.3 ].

Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными директивами #include. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [19.3 ].

Директивы условной компиляции

Каждая директива #if сопровождается директивой #endif.

В общем случае директива #undef выглядит следующим образом:

#undef имя_макроса

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

Для того чтобы узнать определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [19.3 ].

Директива #line

Директива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них содержится номер компилируемой в данный момент строки программного кода программы [

препроцессор . Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке:
  1. Все системно-зависимые обозначения перекодируются в стандартные коды.
  2. Каждая пара из символов " \ " и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов.
  3. В тексте распознаются директивы и лексемы препроцессора, а каждый комментарий заменяется одним символом пустого промежутка.
  4. Выполняются директивы препроцессора и производятся макроподстановки.
  5. Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты.
  6. Смежные символьные строки конкатенируются, то есть соединяются в одну строку.
  7. Каждая препроцессорная лексема преобразуется в текст на языке Си.

Поясним, что понимается под препроцессорными лексемами или лексемами препроцессора. К ним относятся символьные константы , имена включаемых файлов, идентификаторы, знаки операций, препроцессорные числа, знаки препинания, строковые константы и любые символы, отличные от пробела.

Стадия обработки директив препроцессора. При ее выполнении возможны следующие действия:

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

Символические константы: #define

Если в качестве первого символа в строке программы используется символ # , то эта строка является командной строкой препроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой черты " \ ", то командная строка будет продолжена на следующую строку программы.

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

Замена идентификаторов

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

Заменяет каждое вхождение идентификатора ABC в тексте программы на 100 :

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

Отменяет предыдущее определение для идентификатора 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; }

В результате выполнения нашего примера будем иметь.

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

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

Определение

Назначение

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

Отмена определения макроса

Включение объекта-заголовка

Компиляция, если выражение истинно

Компиляция, если макрос определен

Компиляция, если макрос не определен

Компиляция, если выражение в ifложно

Составная директива else/if

Окончание группы компиляции по условию

Замена новым именем строки или имени исходного файла

Формирование ошибок трансляции

Действие определяется реализацией

Null- директива

Директива # define

Директива # define вводит макроопределение или макрос. Общая форма директивы следующая:

# define ИМЯ_МАКРОСА последовательность_символов

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

Можно отменить определение макроса директивой # undef:

# undef имя_макроса

Данная строка удаляет любую ранее введенную строку замещения. Определение макроса теряется и имя_макроса становится неопределенным.

К примеру, можно определить МАХ как величину 100:

Это значение будет подставляться каждый раз вместо макроса МАХ в исходном файле, Можно также использовать макрос вместо строковой константы:

#defineNAME“TurboC++”

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

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

Пример : #define MIN(a, b) ((9a)<(b)) ? (a) : (b)

printf(“Минимум из x и y “ % d, MIN(x ,y));

printf(“Минимум из a и b “ % d, MIN(n ,m));

Когда программа будет компилироваться, в выражение, определенное MIN(a,b) будут подставлены соответственноxиyилиmиn. Аргументыaиbзаключены в круглые скобки, так как вместо них может подставляться некоторое выражение, а не просто идентификатор.

Например, printf(“Минимум “ %d,MIN(x*x,x));

Директива # error

Имеет вид: # error сообщение_об_ошибке

Эта команда прекращает компиляцию программы и выдает сообщение об ошибке.

Директивы условной компиляции

К данным директивам относятся: # if , # else , # elif , # endif .

Данные директивы производят выборочную компиляцию программы. Если выражение, следующее за #if, истинно, то коды, заключенные между #ifи #endif, будут компилироваться. В противном случае они при компиляции будут пропущены. Выражение, следующее за #if, проверяется во время компиляции, поэтому оно может содержать только константы и макросы, которые прежде определены. Переменные здесь не могут использоваться.

Директива # else используется так же, как иelseв языке Си.

Пример: Использование условной компиляции.

# include

# define MAX 100

printf(“ MAX равно %d \n”, MAX);

Директива # elif используется для организации вложенной условной компиляции. Форма использования ее следующая:

#if<выражение>

последовательность операторов

#elif<выражение 1>

последовательность операторов

#elif<выражение 2>

последовательность операторов

…………………………………..

Другой метод условной компиляции состоит в использовании директив # ifdef и# ifndef . Основная форма использования этих директив следующая:

# ifdef ИМЯ_МАКРОСА

# endif

и соответственно

# ifndef ИМЯ_МАКРОСА

последовательность операторов

# endif

Если макрос определен, то при использовании # ifdefкомпилируется соответствующая последовательность до операторов #endif. Если же макрос не определен или был отменен директивой #undef, то соответствующая последовательность операторов игнорируется компилятором. Директива #ifndefдействует противоположным образом.

Введение

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

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

#define #else #if #ifndef #line
#elif #endif #ifdef #include #undef

Символ # должен быть первым в строке, содержащей директиву в СП MSC версии 4. В СП MSC версии 5 ив СП ТС ему могут предшествовать пробельные символы. Как в СП MSC, так и в СП ТС пробельные символы допускаются между символом # и первой буквой директивы.

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

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

В рассматриваемых системах программирования есть возможность получить промежуточный текст программы после работы препроцессора, до начала собственно компиляции. В этом файле уже выполнены макроподстановки, а все строки, содержащие директивы #define и #undef, заменены на пустые строки. На место строк #include подставлено содержимое соответствующих включаемых файлов. Выполнена обработка директив условной компиляции #if, #elif, #else, #ifdef, #ifndef, #endif, а строки, содержащие их, заменены пустыми строками. Пустыми строками заменены и исключенные в процессе условной компиляции фрагменты исходного текста. Кроме того, в этом файле есть строки следующего вида:

#["имя файла"]

которые соответствуют точкам изменения номера текущей строки и/или номера файла по директивам #line или #include.

Именованные константы и макроопределения

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

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

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

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

Имеется возможность задавать определения именованных констант не только в исходном тексте, но и в командной строке компиляции.

Имеется ряд предопределенных идентификаторов, которые нельзя использовать в директивах #define и #undef в качестве идентификаторов. Они рассмотрены в разделе 7.9 "Псевдопеременные".

Директива #define

Синтаксис:

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

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

может быть опущен. В этом случае все экземпляры будут удалены из исходного текста программы. Тем не менее, сам рассматривается как определенный и при проверке директивой #if дает значение 1 (смотри раздел 7.4.1).

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

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

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

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

Внутрь в директиве #define могут быть вложены имена других макроопределений или констант. Их расширение производится лишь при расширении этого, а не при его определении директивой #define. Это надо учитывать, в частности, при взаимодействии вложенных именованных констант и макроопределений с директивой #undef: к моменту расширения содержащего их текста они могут уже оказаться отменены директивой #undef.

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

не приведет к зацикливанию препроцессора.

/* пример 1 */

#define WIDTH 80

#define LENGTH (WIDTH + 10)

/* пример 2 */

#define FILEMESSAGE "Попытка создать файл\

не удалась из-за нехватки дискового пространства"

/* пример 3 */

#define REG1 register

#define REG2 register

/* пример 4 */

#define MAX(x, y)((x)>(у)) ? (x) : (у)

/* пример 5 */

#define MULT(a, b) ((a)*(b))

В первом примере идентификатор WIDTH определяется как целая константа со значением 80, а идентификатор LENGTH - как текст (WIDTH + 10). Каждое вхождение идентификатора LENGTH в исходный файл будет заменено на текст (WIDTH + 10), который после расширения идентификатора WIDTH превратится в выражение (80 + 10). Скобки, окружающие текст (WIDTH + 10), позволяют избежать ошибок в операторах, подобных следующему:

var = LENGTH * 20;

После обработки препроцессором оператор примет вид:

var = (80 + 10)* 20;

Значение, которое присваивается var, равно 1800. В отсутствие скобок в макроопределении оператор имел бы следующий вид:

var = 80 + 10*20;

Значение var равнялось бы 280, поскольку операция умножения имеет более высокий приоритет, чем операция сложения.

Во втором примере определяется идентификатор FILEMESSAGE. Его определение продолжается на вторую строку путем использования символа обратный слэш непосредственно перед нажатием клавиши ENTER.

В третьем примере определены три идентификатора, REG1, REG2, REG3. Идентификаторы REG1 и REG2 определены как ключевые слова register. Определение REG3 опущено и, таким образом, любое вхождение REG3 будет удалено из исходного файла. В разделе 7.4.1 приведен пример, показывающий, как эти директивы могут быть использованы для задания класса памяти register наиболее важным переменным программы.

В четвертом примере определяется макроопределение МАХ. Каждое вхождение идентификатора МАХ в исходном файле заменяется на выражение ((x)>(у))?(x):(у), в котором вместо формальных параметров х и у подставлены фактические. Например, макровызов

заменится на выражение

((1)>(2))?(1):(2)

а макровызов

заменится на выражение

((i)>(s(i]))?(i):(s(i])

Обратите внимание на то, что в этом макроопределении аргументы с побочными эффектами могут привести к неверным результатам. Например, макровызов

заменится на выражение

((i)>(s))?(i):(s)

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

В пятом примере определяется макроопределение MULT. Макровызов MULT(3,5) в тексте программы заменяется на (3)*(5). Круглые скобки, в которые заключаются фактические аргументы, необходимы в тех случаях, когда аргументы макроопределения являются сложными выражениями. Например, макровызов

заменится на (3+4)*(5+6), что равняется 76. В отсутствие скобок результат подстановки 3+4*5+6 был бы равен 29.

Склейка лексем и преобразование аргументов макроопределений

СП ТС и версия 5.0 СП MSC реализуют две специальные препроцессорные операции: ## и #.

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

#define VAR (i, j) i##j

при макровызове VAR(х,6) образует идентификатор х6. Некоторые компиляторы позволяют в аналогичных целях употребить запись х/**/6, но этот метод менее переносим.

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

Пример: макроопределение TRACE позволяет печатать с помощью стандартной функции printf значения переменных типа int в формате = .

#define TRACE(flag) printf (#flag " = %d\n", flag)

Следующий фрагмент текста программы:

TRACE (highval);

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

printf("highval" " = %d\n", highval);

Следующие друг за другом символьные строки рассматриваются компилятором языка Си в СП MSC версии 5 и в СП ТС как одна строка, поэтому полученная запись эквивалентна следующей:

printf("highval = %d\n", highval);

При макровызове сначала выполняется макроподстановка всех аргументов макровызова, а затем их подстановка в тело макроопределения. Поэтому следующая программа напечатает строку "отклонение от стандарта":

#define АВ "стандарт"

#define А "отклонение"

#define В "от стандарта"

#define CONCAT(P,Q) Р##Q

printf(CONCAT(A,В) "\n");

Директива #undef

Синтаксис:

Директива #undef отменяет действие текущего определения #define для. Чтобы отменить макроопределение посредством директивы #undef, достаточно задать его. Задание списка параметров не требуется.

Не является ошибкой применение директивы #undef к идентификатору, который ранее не был определен (или действие его определения уже отменено). Это может использоваться для гарантии того, что идентификатор не определен.

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

#define WIDTH 80

#define ADD(X, Y) (X)+(Y)

В этом примере директива #undef отменяет определение именованной константы WIDTH и макроопределения ADD. Обратите внимание на то, что для отмены макроопределения задается только его идентификатор.

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

Синтаксис:

#include "имя пути"

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

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

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

Препроцессор использует понятие стандартных директорий для поиска включаемых файлов. Стандартные директории задаются командой PATH операционной системы.

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

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

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

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

Затем препроцессор продолжает поиск в директориях, указанных в командной строке компиляции, и, наконец, ищет в стандартных директориях.

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

#include /* пример 1 */

#include "defs.h" /* пример 2 */

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

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

В СП ТС имеется возможность задавать имя пути в директиве #include с помощью именованной константы. Если за словом include следует идентификатор, препроцессор проверяет, не именует ли он константу или макроопределение. Если же за словом include следует строка, заключенная в кавычки или в угловые скобки, СП ТС не будет искать в ней имя константы.

#define myinclude "c:\tc\include\mystuff.h"

#include myinclude

#include "myinclude.h"

Первая директива #include заставит препроцессор просматривать директорию C:\TC\INCLUDE\MYSTUFF.H, а вторая заставит искать файл MYINCLUDE.H в текущей директории.

Объединение символьных строк и склейку лексем в именованной константе, которая используется в директиве #include, использовать нельзя. Результат расширения константы должен сразу читаться как корректная директива #include.

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

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

Директивы #if, #elif, #else, #endif

Синтаксис:

Директива #if совместно с директивами #elif, #else и #endif управляет компиляцией частей исходного файла. Каждой директиве #if в том же исходном файле должна соответствовать завершающая ее директива #endif. Между директивами #if и #endif допускается произвольное количество директив #elif (в том числе ни одной) и не более одной директивы #else. Если директива #else присутствует, то между ней и директивой #endif на данном уровне вложенности не должно быть других директив #elif.

Препроцессор выбирает один из участков для обработки. может занимать более одной строки. Обычно это участок программного текста, однако это не обязательно: препроцессор можно использовать для обработки произвольного текста. Если содержит директивы препроцессора (в том числе и директивы условной компиляции), то эти директивы выполняются. Обработанный препроцессором текст передается на компиляцию.

Участок текста, не выбранный препроцессором, игнорируется на стадии препроцессорной обработки и не компилируется.

Препроцессор выбирает участок текста для обработки на основе вычисления, следующего за каждой директивой #if или #elif. Выбирается, следующий за со значением истина (не нуль), вплоть до ближайшей директивы #elif, #else, или #endif, ассоциированной с данной директивой #if.

Если ни одно ограниченное константное выражение не истинно, то препроцессор выбирает, следующий за директивой #else. Если же директива #else отсутствует, то никакой текст не выбирается.

Ограниченное константное выражение описано в разделе 4.2.9 "Константные выражения". Такое выражение не может содержать операцию sizeof (в СП ТС - может), операцию приведения типа, константы перечисления и плавающие константы, но может содержать препроцессорную операцию defined(). Эта операция дает истинное (не равное нулю) значение, если заданный в данный момент определен; в противном случае выражение ложно (равно нулю). Следует помнить, что идентификатор, определенный без значения, тем не менее рассматривается как определенный. Операция defined может использоваться в сложном выражении в директиве #if неоднократно:

#if defined(mysym) || defined(yoursym)

СП TC (в отличие от СП MSC) позволяет использовать операцию sizeof в ограниченном константном выражении для препроцессора. В следующем примере в зависимости от размера указателя определяется одна из констант - либо SDATA, либо LDATA:

#if (sizeof(void *) == 2)

Директивы #if могут быть вложенными. При этом каждая из директив #else, #elif, #endif ассоциируется с ближайшей предшествующей директивой #if.

/* пример 1 */

#if defined(CREDIT)

#elif defined (DEBIT)

/* пример 2 */

#define SIGNAL 1

#if STACKUSE == 1

#derine STACK 200

#define STACK 100

#define SIGNAL 0

#if STACKUSE == 1

#define STACK 100

#define STACK 50

/* пример 3 */

#elif DLEVEL == 1

#define STACK 100

#elif DLEVEL > 5

display(debugptr);

#define STACK 200

/* пример 4 */

#define REG 1 register

#define REG2 register

#if defined (M_86)

#ifdefined(M_68000)

#define REG4 register

В первом примере директивы #if, #elif, #else, #endif управляют компиляцией одного из трех вызовов функции. Вызов функции credit компилируется, если определена именованная константа CREDIT. Если определена именованная константа DEBIT, то компилируется вызов функции debit. Если ни одна из.именованных констант не определена, то компилируется вызов функции printerror. Следует учитывать, что CREDIT и credit являются различными идентификаторами в языке Си.

В следующих двух примерах предполагается, что константа DLEVEL предварительно определена директивой #define.

Во втором примере показаны два вложенных набора директив #if, #else, #endif. Первый набор директив обрабатывается, если значение DLEVEL больше 5. В противном случае обрабатывается второй набор.

В третьем примере директивы уловной компиляции используют для выбора текста значение константы DLEVEL. Константа STACK определяется со значением 0, 100 или 200, в зависимости от значения DLEVEL. Если DLEVEL больше 5, то компилируется вызов функции display, а константа STACK не определяется.

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

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

В примере показано, каким образом предоставить приоритет регистровой памяти наиболее важным переменным. Именованные константы REG1 и REG2 определяются как ключевые слова register. Они предназначены для объявления двух наиболее важных локальных переменных функции. Например, в следующем фрагменте программы такими переменными являются b и c.

func(REG3 int а)

Если определена константа М_86, препроцессор удаляет идентификаторы REG3 и REG4 из файла путем замены их на пустой текст. Регистровую память в этом случае получат только переменные b и с. Если определен идентификатор М_68000, то все четыре переменные объявляются с классом памяти register.

Если не определена ни одна из констант - ни М_86, ни М_68000, - то регистровую память получат переменные а, b и с.

Директивы #ifdef и #ifndef

Синтаксис:

Аналогично директиве #if, за директивами #ifdef и #ifndef может следовать набор директив #elif и директива #else. Набор должен быть завершен директивой #endif.

Использование директив #ifdef и #ifndef эквивалентно применению директивы #if, использующей выражение с операцией defined(). Эти директивы поддерживаются исключительно для совместимости с предыдущими версиями компиляторов языка Си. Для новых программ рекомендуется использовать директиву #if с операцией defined().

Когда препроцессор обрабатывает директиву #ifdef, он проверяет, определен ли в данный момент директивой #define. Если да, условие считается истинным, если нет - ложным.

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

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

Текущий номер строки и имя исходного файла доступны в программе через псевдопеременные с именами __LINE__ и __FILE__. Эти псевдопеременные могут быть использованы для выдачи во время выполнения сообщений о точном местоположении ошибки.

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

/* пример 1 */

#line 151 "copy.с"

/* пример 2 */

#define ASSERT(cond) if (!cond)\

{printf ("ошибка в строке %d файла %s\n", \

LINE__, __FILE__);} else;

В первом примере устанавливается имя исходного файла сору.с и текущий номер строки 151.

Во втором примере в макроопределении ASSERT используются псевдопеременные __LINE__ и __FILE__ для печати сообщения об ошибке, содержащего координаты исходного файла, если некоторое условие, заданное макроаргументом cond, ложно.

Директива обработки ошибок

В СП ТС реализована директива #error. Ее формат:

Обычно эту директиву записывают среди директив условной компиляции для обнаружения некоторой недопустимой ситуации. По директиве #error препроцессор прерывает компиляцию и выдает следующее сообщение:

Fatal: Error directive:

Fatal - признак фатальной ошибки; - имя исходного файла; - текущий номер строки; Error directive - сообщение об ошибке в директиве; - собственно текст диагностического сообщения.

Указания компилятору языка Си

Синтаксис:

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

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

Псевдопеременные

Псевдопеременные представляют собой зарезервированные именованные константы, которые можно использовать в любом исходном файле. Каждый из них начинается и оканчивается двумя символами подчеркивания (__).

Номер текущей обрабатываемой строки исходного файла-десятичная константа. Первая строка исходного файла имеет номер 1.

Имя компилируемого исходного файла - символьная строка. Значение данной псевдопеременной изменяется каждый раз, когда компилятор обрабатывает директиву #include или директиву #line, а также по завершении включаемого файла.

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

Дата начала компиляции текущего исходного файла - символьная строка. Каждое вхождение __DATE__ в заданный файл дает одно и то же значение, независимо от того, как долго уже продолжается обработка. Дата имеет формат mmm dd УУУУ, где mmm - месяц (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), dd - число текущего месяца (1…31; в 1-й позиции dd ставится пробел, если число меньше 10), уууу - год (например, 1990).

Время начала компиляции текущего исходного файла - символьная строка. Каждое вхождение __TIME__ в заданный файл дает одно и то же значение, независимо от того, как долго уже продолжается обработка. Время имеет формат hh:mm:ss, где hh - час (00…23), mm - минуты (00…59), ss - секунды (00…59).

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

Основные директивы препроцессора

#include - вставляет текст из указанного файла
#define - задаёт макроопределение (макрос) или символическую константу
#undef - отменяет предыдущее определение
#if - осуществляет условную компиляцию при истинности константного выражения
#ifdef - осуществляет условную компиляцию при определённости символической константы
#ifndef - осуществляет условную компиляцию при неопределённости символической константы
#else - ветка условной компиляции при ложности выражения
#elif - ветка условной компиляции, образуемая слиянием else и if
#endif - конец ветки условной компиляции
#line - препроцессор изменяет номер текущей строки и имя компилируемого файла
#error - выдача диагностического сообщения
#pragma - действие, зависящее от конкретной реализации компилятора.

Директива #include

Директива #include позволяет включать в текст программы указанный файл. Если файл является стандартной библиотекой и находится в папке компилятора, он заключается в угловые скобки <> .
Если файл находится в текущем каталоге проекта, он указывается в кавычках "" . Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь.

#include
#include "func.c"

Директива #define

Директива #define позволяет вводить в текст программы константы и макроопределения.
Общая форма записи

#define Идентификатор Замена


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

1
2
3
4
5
6
7
8

#include
#define A 3
int main()
{
printf("%d + %d = %d" , A, A, A+A); // 3 + 3 = 6
getchar();
return 0;
}

В зависимости от значения константы компилятор присваивает ей тот или иной тип. С помощью суффиксов можно переопределить тип константы:

  • U или u представляет целую константу в беззнаковой форме (unsigned );
  • F (или f ) позволяет описать вещественную константу типа float ;
  • L (или l ) позволяет выделить целой константе 8 байт (long int );
  • L (или l ) позволяет описать вещественную константу типа long double

#define A 280U // unsigned int
#define B 280LU // unsigned long int
#define C 280 // int (long int)
#define D 280L // long int
#define K 28.0 // double
#define L 28.0F // float
#define M 28.0L // long double

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

идентификатор(аргумент1, ..., агрументn)


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

Пример на Си : Вычисление синуса угла

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include
#include
#include
#define PI 3.14159265
#define SIN(x) sin(PI*x/180)
int main()
{
int c;
system("chcp 1251" );
system("cls" );
printf("Введите угол в градусах: " );
scanf("%d" , &c);
printf("sin(%d)=%lf" , c, SIN(c));
getchar(); getchar();
return 0;
}

Результат выполнения

Отличием таких макроопределений от функций в языке Си является то, что на этапе компиляции каждое вхождение идентификатора замещается соответствующим кодом. Таким образом, программа может иметь несколько копий одного и того же кода, соответствующего идентификатору. В случае работы с функциями программа будет содержать 1 экземпляр кода, реализующий указанную функцию, и каждый раз при обращении к функции ей будет передано управление.
Отменить макроопределение можно с помощью директивы #undef .

Однако при использовании таких макроопределений следует соблюдать осторожность, например

1
2
3
4
5
6
7
8
9
10
11
12
13

#include
#define sum(A,B) A+B
int main()
{
int a, b, c, d;
a = 3; b = 5;


getchar();
return 0;
}


Результат выполнения:


По умолчанию текст макроопределения должен размещаться на одной строке. Если требуется перенести текст макроопределения на новую строку, то в конце текущей строки ставится символ "обратный слеш" — \ .

1
2
3
4
5
6
7
8
9
10
11
12
13
14

#include
#define sum(A,B) A + \
B
int main()
{
int a, b, c, d;
a = 3; b = 5;
c = (a + b) * 2; // c = (a + b)*2
d = sum(a, b) * 2; // d = a + b*2;
printf(" a = %d\n b = %d\n" , a, b);
printf(" c = %d \n d = %d \n" , c, d);
getchar();
return 0;
}


Кроме того, директива #define позволяет замещать часть идентификатора. Для указания замещаемой части используется ## .

1
2
3
4
5
6
7
8
9

#include
#define SUM(x,y) (a##x + a##y)
int main()
{
int a1 = 5, a2 = 3;
printf("%d" , SUM(1, 2)); // (a1 + a2)
getchar();
return 0;
}


Результат выполнения:

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

1
2
3
4
5
6
7

#if константное выражение
группа операций
#elif константное выражение
группа операций
#else
группа операций
#endif


Отличие директив #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define .

У каждой директивы #if в исходном файле должна быть соответствующая закрывающая директива #endif . Между директивами #if и #endif может располагаться любое количество директив #elif , однако допускается не более одной директивы #else . Директива #else , если присутствует, должна быть последней перед директивой #endif .

Пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include
#include
#define P 2
int main()
{
system("chcp 1251" );
system("cls" );
#if P==1
printf("Выполняется ветка 1" );
#elif P==2
printf("Выполняется ветка 2, P=%d" , P);
#else
printf("Выполняется другая ветка, P=%d" , P);
#endif
getchar();
return 0;
}



Рекомендуем почитать

Наверх