Что такое директива в c
Перейти к содержимому

Что такое директива в c

  • автор:

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

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

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

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

Директива #define

Директива #define принимает две формы:

  • определение констант;
  • определение макросов.
Определение констант

При использовании имени константы — nameToken , оно будет заменено значением value , то есть, грубо говоря — это та же самая переменная, значение которой изменить нельзя. Смотрим пример использования константы:

Как видите, для доступа к значению константы, просто используем её имя.

Определение параметризованных макросов

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

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

Директива #undef

Директива #undef переопределяет константу или препроцессорный макрос, ранее определенный с помощью директивы #define .

Давайте посмотрим пример использования директивы #undef :

Как правило, директива #undef используются для снятия, ранее определенной константы или макроса, в небольшой области программы. Это делается для того, чтобы для всей программы, макроc или константа оставались, а для некоторой области, эти же макрос или константа могут быть переопределены. Небезопасно было бы во всей программе переопределять константу, но в короткий области, это сравнительно безопасно. Директива #undef является единственным способом создания этой области, так как область действия макросов или констант действует от директивы #define до #undef .

Директива #if

Директива #if проверяет, является ли значение value истиной и, если это так, то выполняется код, который стоит до закрывающей директивы #endif . В противном случае, код внутри #if не будет компилироваться, он будет удален компилятором, но это не влияет на исходный код в исходнике.

Обратите внимание, что в #if могут быть вложенные директивы #elsif и #else . Ниже показан пример кода для комментирования блоков кода, используя следующую конструкцию:

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

Директива #ifdef

Директива #ifdef проверяет, был ли ранее определен макрос или символическая константа как #define . Если — да, компилятор включает в программу код, который находится между директивами #ifdef и #else , если nameToken ранее определен не был, то выполняется код между #else и #endif , или, если нет директивы #else , компилятор сразу переходит к #endif . Например, макрос __cpp определен в C++, но не в Си. Вы можете использовать этот факт для смешивания C и C++ кода, используя директиву #ifdef :

Директива #ifndef

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

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

В этом случае используется пустая символьная константа PRODUCT_H , которая может быть определена в программе только вместе с классом Product . Поэтому, если мы обнаружим, что константа PRODUCT_H уже определена, значит класс тоже и тогда мы исключим повторное определение класса, которое может привести к ошибке переопределения.

Директива #error

Директива #error позволяет отображать в списке ошибок компиляции сообщение, в случае возникновения соответствующей ошибки. Эту директиву наиболее полезно использовать в сочетании с директивами #if , #elsif , #else для проверки компиляции, если некоторое условие не верно. Например:

Препроцессорный макрос __FILE__

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

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

Препроцессорный макрос __LINE__

Макрос __LINE__ разворачивается в текущий номер строки в исходном файле, как целое значение. __LINE__ полезен при создании лог-файла или генерации сообщений об ошибках с указанием номера строки, предназначенных для программистов, при отладки кода.

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

Препроцессорный макрос __DATE__

Макрос __DATE__ раскрывается в текущую дату (время компиляции) в виде [ммм дд гггг] (например, «Dec 7 2012″), как строка. __DATE__ может быть использован для предоставления информации о времени компиляции.

Вы можете также использовать макрос __TIME__ , чтобы получить текущее время компиляции.

Препроцессорный макрос __TIME__

Макрос __TIME__ раскрывается в текущее время (время компиляции) в формате чч: мм:cc в 24-часовом формате (например, «22:29:12″). Макрос __TIME__ может быть использован для предоставления информации о времени в конкретный момент компиляции.

Препроцессорный макрос __TIMESTAMP__

Макрос __TIMESTAMP__ раскрывается в текущее время (время компиляции) в формате Ddd Mmm Date hh::mm::ss yyyy, время в 24-часовом формате:

  • Ddd это сокращенно день недели,
  • ммм это сокращенно месяц ,
  • Date — текущий день месяца (1-31),
  • гггг — это четыре цифры года.

Например, «Fri Dec 7 00:42:53 2012» . Макрос __TIMESTAMP__ может быть использован для получения информации о дате и времени компиляции.

Вы можете также использовать макрос __TIME__ , чтобы получить текущее время компиляции и макрос __DATE__ для получения даты.

Директива #pragma

Директива #pragma используется для доступа к специфическим расширениям компилятора. Совместное использование директивы #pragma c лексемой once просит компилятор включить файл заголовка только один раз, независимо от того, сколько раз она был импортирован:

В этом примере, директива #pragma once не позволяет включать файл в проект несколько раз, то есть предотвращает переопределение.

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

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

Макро оператор #

Оператор # текстовую лексему в строку, заключенную в кавычку. Смотрим пример:

Выполняется конкатенация строк и макрос message разворачивается в cout << «Сообщение: GunGamen»; . Обратите внимание на то, что операция # должна использоваться совместно с аргументами, так как # ссылается на аргумент.

Макро оператор ##

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

Рассмотрим еще один пример использования оператора ## , в котором объединим две лексемы:

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

Препроцессор C

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

Как работает препроцессор.

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

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

Директивы — это специальные команды, которые препроцессор распознаёт и выполняет. Все директивы начинаются со знака #. Если первый непробельный символ в строке это — #, то препроцессор будет пытаться распознать в ней свою директиву.
Существуют следующие директивы:
— Подключение файлов: #include, #include_next.
— Условная компиляция: #if, #ifdef, #ifndef, #else, #elif and #endif.
— Диагностика: #error, #warning, #line.
— Дополнительная информация компилятору: #pragma
— Макроопределения: #define

Подключение файлов.

Первая директива, которая всем встречается при изучении языка Си — это #include. Записывается так:

Встретив в исходнике эту директиву, препроцессор заменяет её на содержимое файла, имя которого указанно в параметре. Различие между первой и второй формой записи состоит в том, где в первую очередь, препроцессор будет искать указанный файл. В первом случае он сначала будет искать в каталогах с системными заголовками. Во втором — в том-же каталоге, где находится компилируемый исходник. Грубо говоря, при подключении системных/стандартных заголовков нужно имя файла писать в угловых скобках, а для своих — в кавычках.
Мало кто знает, но есть ещё одна директива для включения файлов — #include_next. Записывается она также как и обычный #include, но ее поведение несколько отличается. Дело в том, что препроцессор ищет подключаемые заголовки по многим путям, и бывает, что искомый файл есть сразу в нескольких каталогах. В случае применения директивы #include, он подключает первый попавшийся файл с совпавшим именем. В случае #include_next — будет подключен первый файл с совпавшим именем, который еще не включался в эту единицу трансляции, то есть следующий еще не подключенный. Причем применять #include_next можно только в этих самых заголовках с совпадающими именами, применённая в.с файле эта директива ведёт себя как обычный #include. Таким образом, если в каждом из заголовков с одинаковыми именами применить #include_next, то конечном итоге, они все будут подключены.
Ещё одна интересная особенность директивы #include то, что в ней тоже выполняется макроподстановка. То есть, параметра в ней можно использовать любой макрос, который развернётся в имя файла в одной из двух допустимых форм(в кавычках или в угловых скобках). Например:

В данном случае макрос в директиве include развернётся в #include <avr/io.h>. Для склеивания частей пути к файлу использован трюк с пустым комментарием. Таким образом можно, например, выбирать каталог для подключения системно-зависимых заголовков. Хотя пример несколько надуманный и выбирать каталоги для системно-зависимых заголовков лучше в системе сборки проекта через соответствующие ключи компилятору. Еще эту технику можно использовать для того, чтобы подключить в определенном месте заголовок, имя которого определено где-то далеко, например в недрах какой-то библиотеки.

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

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

Где условие — это выражение препроцессора. Это может быть любая комбинация макросов, условий и целочисленных литералов, которая в результате макроподстановки превратится в выражение состоящее только из целочисленных литералов, арифметических операций и логических операторов. Так-же здесь ещё можно использовать единственный «макрооператор» — defined — он превращается в 1, если его операнд определён, и 0 — если нет.

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

В обоих приведённых примерах условия будут всегда ложны и содержимое #if блоков не выполнится. Препроцессор не знает ничего, ни о структурах и их размере, ни о переменных — они-ж не макросы. По этому в первом случае нужно использовать static_assert, реализованный средствами самого Си. А вот во втором случае можно извернутся так:

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

В данном примере блок #if выполнится если макрос BUFFER_SIZE имеет значение кратное степени двойки и если определен макрос OPTIMIZE_FOR_POWER_OF_2. Конструкция IS_POWER_OF_2(BUFFER_SIZE) после макроподстановки развернется в выражение ((16) & (16)-1 == 0), которое препроцессор легко вычислит.
Для конструкции типа #if defined есть сокращенная форма: #ifdef. Она во всём эквивалентна полной форме, за исключением того, что в сокращенной форме нельзя комбинировать несколько условий.
Также директивы условной компиляции часто используются для предотвращения повторного включения заголовочных файлов (include guard):

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

Диагностика.

В предыдущих примерах мы уже встретились с одной диагностической директивой — #error. Назначение её предельно просто — остановить компиляцию с сообщением об ошибке, указанном после директивы. Её можно использовать совместно с директивами условной компиляции для того, чтоб убедиться установлен ли какой-то важный макрос и, что он имеет правильное значение.
Также существует директива #warning, аналогична #error, но не прерывает компиляцию, а выдаёт предупреждение.
Директива #line служит для задания номеров строк и имени файла, показываемых в сообщениях об ошибках и возвращаемые специальными макросами __LINE__ и __FILE__. Например:

При этом в сообщениях об ошибках мы увидим следущее:

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

#pragma

Эта директива используется в основном для передачи компилятору различных нестандартных опций, таких как уровни оптимизаций для отдельных частей программы, выравнивания структур, параметров компоновщика и много чего ещё. У каждого компилятора свой набор директив pragma, но есть и стандартные, поддерживаемые многими компиляторами. Например, директива #pragma once . Это замена для include guard, встретив в заголовке эту директиву, препроцессор предотвратит повторное включение этого заголовка в обрабатываемую единицу трансляции.

Макроопределения

Теперь переходим к интересному, собственно к макросам. существуют два типа макросов: макрос-объект(object-like macro) и макрос-функция(function-like macro), оба типа объявляются с помощью директивы #define. Рассмотрим сначала макросы-объекты. Объявляются они как:
#define ИМЯ_МАКРОСА [замещающий текст]
Всё, что идёт после имя макроса до конца строки является замещающим текстом.

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

Когда препроцессор будет обрaбатывать строчку:
char buffer[DOUBLE_BUFFER];
Сначала будет выполнена первая макроподстановка и токен DOUBLE_BUFFER будет заменен на EXTRA_BUFFER * 2. Тут-же будет выполнена вторая макроподстановка и токен EXTRA_BUFFER заменется на (BUFFER_SIZE +10), потом BUFFER_SIZE заменется на 32. В результате вся строчка после препроцессинга будет выглядеть так:

Здесь становится понятно, зачем были нужны скобки в макросе EXTRA_BUFFER, без них результирующее выражение получилось бы таким:

А это явно не то, что мы хотели получить. Отсюда правило:
Если макрос содержит какое-то выражение, то оно обязательно должно быть заключено в скобки, иначе могут происходить всякие неочевидные вещи.
Также важно понимать, что препроцессор сам ничего не вычисляет (кроме как в условных директивах #if), он просто склеивает текстовые строчки.
А что будет, если какой-то макрос будет ссылаться сам на себя, непосредственно, и косвенно через другие макросы? Ничего не будет, рекурсии не получится, как только препроцессор просечет рекурсию, он прекратит макроподстановку макроса её вызвавшего и оставит его как есть. Например:

При этом будет определён и символ препроцессора flags и переменная flags. такую особенность часто используют для того, чтобы иметь возможность проверить наличие переменной(или любого другого идентификатора) с помощью директив условной компиляции #ifdef/#else/#endif:
// если флаги определены

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

Предопределённые макросы

У каждого компилятора есть множество предопределённых макросов, есть стандартные — общие для всех: gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html#Standard-Predefined-Macros
Есть специфичные для каждого отдельного компилятора, например у gcc:
gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros
И даже для каждой поддерживаемой платформы, например для avr-gcc, список всех предопределённых макросов(кроме контекстно зависимых, таких как __LINE__ и т.д) можно получить набрав в командной строке:

Соответственно, вместо atmega16 писать интересующий контроллер.
В других компиляторах предопределённые макросы ищутся в соответствующей документации.
Все эти макросы могут использоваться для определения платформы, для которой компилируется программа, используемого языка (Си, Си++ или ассемблер) и различных особенностей целевой архитектуры.
Также есть макросы предназначенные в основном для отладки: __FILE__, __LINE__ и __FUNCTION__. __FILE__ разворачивается в строковый литерал, содержащий имя обрабатываемого файла. __LINE__ — целочисленный литерал означающий номер текущей строки. __FUNCTION__ — имя текущей функции. Надо заметить, что макрос __FUNCTION__ разворачивается всё-таки не препроцессором а компилятором — препроцессор ничего не знает о функциях в языке Си. Также надо учитывать, что значения __LINE__ и __FILE__ могут изменяться с помощью директивы #line.
Типичное использование макросов __LINE__, __FILE__ и __FUNCTION__:

При этом вызов функции MyError превратится во что-то такое:

Макросы-функции

Второй вид макросов — это макро-функции (function-like macros). Определяются они с помощью той-же директивы #define, после которой (сразу без пробелов) в круглых скобках идёт список разделённых запятыми аргументов:

Макрос SQR предназначен вычислять квадрат переданного ему выражения, в приведённом примере SQR(b) развернётся в (b * b). Вроде-бы нормально, но если этому макросу передать более сложное выражение
,
то он развернётся совсем не в то, что нужно:

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

Однако и этот вариант не свободен от недостатков, например:

Переменная b будет инкрементирована два раза. И у этого недостатка есть решения гибкие и не очень, стандартные и нет, но о них говорить не будем. В данном примере гораздо лучше применить встраиваемую (inline) функцию, она свободна от недостатков макросов:

У макросов-функций есть интересная особенность — макроподстановка в них выполняется два раза. Первый раз — для каждого из параметров до того, как они будут подставлены в тело макроса. Второй раз — для всего тела макроса после подстановки в него параметров. В большинстве случаев это не имеет особого значения. Подробнее об этом можно прочитать здесь:
gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html#Argument-Prescan

В макро-функциях можно использовать два специальных макро-оператора: # и ##. Первый превращает свой операнд в строковый литерал:

Вызов PRINT_VAR в данном случае превратится в

При этом будет напечатана строка: my_var = 10. Здесь для склеивания форматной строки использован тот факт, что две строки разделённые лишь пробельными символами компилятор считает одной строкой: "%s = %" «d».
Макро-оператор ## склеивает два токена в один токен, для которого после будет выполнена макроподстановка:

Применять эти макро-операторы можно только к параметрам макросов. Причем для параметров к которым они применены макроподстановка будет применена только один раз — для полученного результата. То есть параметр PORT_LETTER не будет отдельно сканироваться на наличие в нем макросов. Почему макрос SET_PIN состоит из двух уровней объясняется ниже.
Теперь, допустим, нам нужен макрос, который склеивает идентификатор из двух кусков:

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

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

Макро-функции можно передать имя другой макро-функции в качестве параметра и, соответственно, вызвать её:

Практический пример препроцессорного метапрограммирования

В качестве примера рассмотрим генерацию таблицы для вычисления контрольной суммы CRC16. Функция для вычисления CRC16 для каждого байта выглядит так:

Где newchar — очередной байт сообщения для которого вычисляем CRC,
crcval — предыдущее значение CRC.
сrcTable — таблица из 256 значений, которую нам надо сгенерировать.
Функция возвращает новое значение контрольной суммы.

Первоначальная идея была и вовсе вычислять CRC16 от строкового литерала с помощью препроцессора, чтобы можно было реализоват «switch» по CRC16 от строки, с удобочитаемыми метками. Но только на препроцессоре это сделать не получилось из-за степенной сложности генерируемых выражений — компилятору банально не хватает памяти, чтоб посчитать таким образом CRC16 для двух символов. На шаблонах С++ это можно сделать без проблем.

Елементы таблицы сrcTable можно вычислить с помощью такой функции:

Где v — индекс в таблице,
polynom — полином контрольной суммы, в данном примере будем использовать значение 0x8408, соответствующее стандарту CRC-CCITT.

Теперь нужно этот алгоритм реализовать с помощью препроцессора. Как быть с циклом? В препроцессоре нет ни циклов ни рекурсии. Прийдётся цикл развернуть вручную:

Теперь, вызывая макрос CRC_TABLE_8 мы получаем константное выражение для одного элемента таблицы. Выражение это, кстати, очень длинное порядка 200-400 тысяч символов! Это происходит потому, что каждый(кроме первого) макрос CRC_TABLE_x вызывает 3 макроса более нижнего уровня, а ведь препроцессор сам выражения не вычисляет, оставляя это компилятору. И получается в результате длинна такого выражения порядка 3 в восьмой степени помножить на длинны выражения низшего уровня. Но ничего, это компилятор еще прожевывает. Теперь нужно сгенерировать саму таблицу:
#define CRC_POLYNOM 0x8408

Можно, конечно оставить и так, но есть решение получше, называется — библиотека Boost preprocessor. В ней имеется много всяких полезняшек, в частности есть макрос BOOST_PP_REPEAT, который повторяет заданное количество раз макрос, переданный ему в качестве параметра. С использованием BOOST_PP_REPEAT геерацию таблицы можно написать так:

Выглядит уже вполне неплохо. Макрос, который будет повторяться в BOOST_PP_REPEAT, должен иметь три параметра. Первый уровень вложенности повторения, если мы будем использовать вложенные повторения, мы его не используем. Второй — счётчик, текущая итерация — индекс в нашей таблице. Третий — дополнительный параметр, мы в нем передаём полином контрольной суммы.

Как-же работает BOOST_PP_REPEAT, если в перпроцессоре нет ни циклов, ни рекурсии. Очень просто — определено 256 макросов с именами типа BOOST_PP_REPEAT_x, где х — номер итерации, которые вызывают друг друга по цепочке. В макросе BOOST_PP_REPEAT склеивается имя макроса этой цепочки из токена BOOST_PP_REPEAT_ и количества требуемых повторений. Это несколько упрощенное объяснение, в реальности там чуть сложнее, но основной принцип такой.

Preprocessor

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

Directives

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

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

    Директивы модуля и импорта также являются директивами предварительной обработки.

    Директивы предварительной обработки не должны исходить из расширения макросов.

    Capabilities

    Препроцессор имеет возможности перевода исходных файлов:

    • условно компилировать части исходного файла (контролируется директивами #if , #ifdef , #ifndef , #else , #elif , #elifdef , #elifndef (начиная с C++ 23) и #endif ).
    • заменить текстовые макросы при возможном объединении или заключении в кавычки идентификаторов (управляется директивами #define и #undef и операторами # и ## )
    • включить другие файлы (контролируется директивой #include и проверяется с помощью __has_include (начиная с C ++ 17))
    • вызвать ошибку или предупреждение (начиная с C++23) (контролируется директивой #error или #warning соответственно (начиная с C++23))

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

    • поведение, определяемое реализацией (управляется директивой #pragma и оператором _Pragma (начиная с C++11)). Кроме того, некоторые компиляторы поддерживают (в разной степени) оператор __pragma в качестве non-standard extension.
    • информация об имени файла и строке, доступная препроцессору (управляется директивой #line )

    Defect reports

    Следующие отчеты о дефектах,изменяющих поведение,были применены ретроактивно к ранее опубликованным стандартам C++.

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

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

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

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

    Директива #include

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *