2.9 – Знакомство с препроцессором
Когда вы компилируете свой код, вы можете ожидать, что компилятор компилирует код именно в том виде, как вы его написали. На самом деле это не так.
Перед компиляцией файл кода проходит этап, известный как трансляция. На этапе трансляции происходит много всего, чтобы подготовить ваш код к компиляции (если вам интересно, здесь вы можете найти список этапов трансляции). Файл кода с примененными к нему трансляциями называется единицей трансляции.
Самый примечательный из этапов трансляции связан с препроцессором. Препроцессор лучше всего рассматривать как отдельную программу, которая манипулирует текстом в каждом файле кода.
Когда препроцессор запускается, он просматривает файл кода (сверху вниз) в поисках директив препроцессора. Директивы препроцессора (часто называемые просто директивами) – это инструкции, которые начинаются с символа # и заканчиваются символом новой строки (НЕ точкой с запятой). Эти директивы сообщают препроцессору, что нужно выполнить определенные задачи по обработке текста. Обратите внимание, что препроцессор не понимает синтаксис C++ – вместо этого директивы имеют свой собственный синтаксис (который иногда напоминает синтаксис C++, а иногда не очень).
Язык Си с нуля — Урок 48 — Директивы препроцессора: define, ifdef, ifndef, if, elif, undef, include
Выходные данные препроцессора проходят еще несколько этапов трансляции, а затем компилируются. Обратите внимание, что препроцессор никоим образом не изменяет исходные файлы кода – скорее, все изменения текста, сделанные препроцессором, временно размещаются в памяти при каждой компиляции файла кода.
В этом уроке мы обсудим, что делают некоторые из наиболее распространенных директив препроцессора.
В качестве отступления.
Директивы using (представленные в уроке «2.8 – Конфликты имен и пространства имен») не являются директивами препроцессора (и, следовательно, препроцессором не обрабатываются). Таким образом, хотя термин директива обычно означает директиву препроцессора, это не всегда так.
Включения
Вы уже видели в действии директиву #include (обычно это было #include ). Когда вы включаете файл с помощью #include , препроцессор заменяет директиву #include содержимым включенного файла. Включенное содержимое затем предварительно обрабатывается (вместе с остальной частью файла), а затем компилируется.
Рассмотрим следующую программу:
#include int main()
Когда препроцессор запускается для этой программы, он заменяет #include предварительно обработанным содержимым файла с именем » iostream «.
Поскольку #include почти всегда используется для включения заголовочных файлов , мы обсудим эту директиву более подробно в следующем уроке (когда мы будем подробнее обсуждать заголовочные файлы).
Определения макросов
Директива #define может использоваться для создания макросов. В C++ макрос – это правило, определяющее, как входной текст преобразуется в выходной текст с помощью замены.
Существует два основных типа макросов: макросы, подобные объектам, и макросы, подобные функциям.
Препроцессор что это. Директива #define. Макросы. Директивы препроцессора что это. C ++ Урок #66
Макросы, подобные функциям, действуют как функции и служат той же цели. Мы не будем здесь их обсуждать, потому что их использование обычно считается опасным, и почти всё, что они могут сделать, можно сделать и с помощью обычных функций.
Макросы, подобные объектам, можно определить одним из двух способов:
#define идентификатор #define идентификатор подставляемый_текст
В первом определении нет подставляемого при замене текста, а во втором есть. Поскольку это директивы препроцессора (а не инструкции), обратите внимание, что ни одна из форм не заканчивается точкой с запятой.
Макросы, подобные объектам, с подставляемым текстом
Когда препроцессор встречает эту директиву, любое дальнейшее появление идентификатора заменяется подставляемым текстом. Идентификатор традиционно набирается заглавными буквами с использованием подчеркивания для обозначения пробелов.
Рассмотрим следующую программу:
#include #define MY_NAME «Alex» int main()
Препроцессор преобразует приведенный выше код в следующее:
// Сюда вставляется содержимое iostream int main()
Этот код при запуске печатает: My name is: Alex .
Объектоподобные макросы использовались как более дешевая альтернатива постоянным переменным. Те времена давно прошли, поскольку компиляторы стали умнее, а язык вырос. Теперь объектоподобные макросы можно увидеть только в устаревшем коде.
Мы рекомендуем вообще избегать таких макросов, так как существуют более эффективные способы сделать аналогичные вещи. Мы обсудим это более подробно в уроке «4.14 – const, constexpr и символьные константы».
Макросы, подобные объектам, без подставляемого текста
Макросы, подобные объектам, также могут быть определены без подставляемого при замене текста.
#define USE_YEN
Макросы этого типа работают так, как и следовало ожидать: любое дальнейшее появление идентификатора удаляется и ничем не заменяется!
Это может показаться довольно бесполезным, и, да, это бесполезно для замены текста. Однако эта форма директивы обычно используется для другого. Мы обсудим использование этой формы чуть позже.
В отличие от объектоподобных макросов с заменяющим текстом, макросы этой формы обычно считаются приемлемыми для использования.
Условная компиляция
Директивы препроцессора условной компиляции позволяют указать, при каких условиях что-то будет или не будет компилироваться. Существует довольно много разных директив условной компиляции, но здесь мы рассмотрим только три, которые используются чаще всего: #ifdef , #ifndef и #endif .
Директива препроцессора #ifdef позволяет препроцессору проверять, был ли идентификатор ранее определен с помощью #define . Если это так, код между #ifdef и соответствующим #endif компилируется. В противном случае код игнорируется.
Рассмотрим следующую программу:
#include #define PRINT_JOE int main() < #ifdef PRINT_JOE std::cout
#ifndef – это противоположность #ifdef в том, что он позволяет вам проверить, НЕ был ли идентификатор еще определен с помощью #define .
#include int main()
Эта программа печатает « Bob », потому что PRINT_BOB никогда не был определен с #define .
Вместо #ifdef PRINT_BOB и #ifndef PRINT_BOB вы также можете увидеть #if defined (PRINT_BOB) и #if !defined(PRINT_BOB) . Они делают то же самое, но используют синтаксис, немного более похожий на C++.
#if 0
Еще одно распространенное использование условной компиляции включает использование #if 0 для исключения блока кода из компиляции (как если бы он находился внутри блока комментариев):
#include int main() < std::cout
Приведенный выше код печатает только « Joe », потому что « Bob » и « Steve » находились внутри блока #if 0 , который препроцессор исключил из компиляции.
Это обеспечивает удобный способ «закомментировать» код, содержащий многострочные комментарии.
Макросы, подобные объектам, не влияют на другие директивы препроцессора.
Теперь вам может быть это интересно:
#define PRINT_JOE #ifdef PRINT_JOE // .
Поскольку мы определили PRINT_JOE как ничто, почему препроцессор не заменил PRINT_JOE в #ifdef PRINT_JOE ничем?
Макросы вызывают замену текста только в обычном коде. Другие команды препроцессора игнорируются. Следовательно, PRINT_JOE в #ifdef PRINT_JOE не меняется.
#define FOO 9 // Вот макрос-подстановка #ifdef FOO // Этот FOO не заменяется, потому что он является частью другой директивы препроцессора std::cout
На самом деле вывод препроцессора вообще не содержит директив – все они разрешаются/удаляются перед компиляцией, потому что компилятор не знает, что с ними делать.
Область видимости определений #define
Директивы разрешаются перед компиляцией сверху вниз для каждого файла.
Рассмотрим следующую программу:
#include void foo() < #define MY_NAME «Alex» >int main()
Несмотря на то, что похоже, что #define MY_NAME «Alex» определен внутри функции foo , препроцессор этого не заметит, поскольку он не понимает таких понятий C++, как функции. Следовательно, эта программа ведет себя идентично программе, в которой #define MY_NAME «Alex» был определен либо до, либо сразу после функции foo . Для большей удобочитаемости идентификаторы определяются с помощью #define обычно вне функций.
После завершения препроцессора все определенные в данном файле идентификаторы отбрасываются. Это означает, что директивы действительны только с точки определения до конца файла, в котором они определены. Директивы, определенные в одном файле кода, не влияют на другие файлы кода в том же проекте.
Рассмотрим следующий пример:
#include void doSomething()
void doSomething(); // предварительное объявление для функции doSomething() #define PRINT int main()
Приведенная выше программа напечатает:
Not printing!
Несмотря на то, что PRINT был определен в main.cpp , это не повлияло на код в function.cpp ( PRINT определен только от точки определения до конца main.cpp ). Это будет иметь значение, когда будем обсуждать защиту заголовков в будущем уроке.
Источник: radioprog.ru
#define
Директива #define определяет идентификатор и последовательность символов, которой будет замещаться данный идентификатор при его обнаружении в тексте программы. Идентификатор также называется именем макроса, а процесс замещения называется подстановкой макроса. Стандартный вид директивы следующий:
#define имя_макроса последовательность_символов
Обратим внимание, что в данном операторе отсутствует точка с запятой. Между идентификатором и последовательностью символов может быть любое число пробелов. Макрос завершается только переходом на новую строку.
Например, если необходимо использовать TRUE для значения 1, a FALSE для 0 то можно объявить следующие два макроса:
#define TRUE 1
#define FALSE 0
В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то он заменит их на 1 и 0 соответственно. Например, следующая строка выводит на экран «0 1 2»:
При компиляции программы вместо MIN(a, b) подставляется указанное выражение, причем вместо фиктивных параметров а и b подставляются реальные х и у. Таким образом, в результате подстановки функция printf() примет следующий вид:
Надо быть осторожным при определении макросов, получающих аргументы, или можно получить несколько неожиданные результаты. Например, рассмотрим следующую короткую программу, использующую макрос для определения четности значения:
/* программа выдает неправильный результат */
#include
#define EVEN(a) a%2==0 ? 1 : 0
int main(void)
if (EVEN(9+1) ) printf(«is even»);
else printf («is odd»);
return 0;
>
Из-за способа подстановки данная программа работает неправильно. В результате компиляции программы EVEN(9 + 1) расширится до
9 + 1% 2 == 0 ? 1 : 0
Как известно, оператор взятия по модулю имеет более высокий приоритет, чем оператор сложения. Это означает, что сначала выполнится взятие по модулю с числом 1, а затем результат прибавится к 9, что, естественно, не может быть равно 0. Для устранения данной проблемы следует заключить а в макросе EVEN в круглые скобки, как показано в следующей правильной версии программы:
#include
#define EVEN(a) (a)%2==0 ? 1 : 0
int main(void)
if(EVEN(9 + 1) ) printf(«is even»);
else printf(«is odd»);
return 0;
>
Обратим внимание, что 9+1 вычисляется до взятия по модулю. В целом заключение параметров макроса в скобки — это достаточно хорошая идея, и она позволяет избежать множества проблем.
Использование макроподстановок вместо реальных функций имеет одно большое преимущество — существенно увеличивается скорость работы программы, поскольку нет необходимости тратить время на вызов функции и возврат из нее. Тем не менее, за данное увеличение скорости работы следует платить увеличением размера исполнимого кода программы, поскольку программа вынуждена дублировать код макроса.
Источник: www.c-cpp.ru
Директива #define (C/C++)
#Define создает макрос, который представляет собой ассоциацию идентификатора или параметризованного идентификатора со строкой токена. После определения макроса компилятор может подставить строку токена для каждого обнаруженного идентификатора в исходном файле.
Синтаксис
#define токена идентификатора — строкаOPT
идентификатор#define(идентификаторOPT, . ,идентификаторOPT)— строка снеявной строкой
Комментарии
Директива #define заставляет компилятор заменить строку токена для каждого вхождения идентификатора в исходном файле. Идентификатор заменяется только в том случае, если он формирует маркер. Это значит, что идентификатор не заменяется, если он присутствует в комментарии, в строке или в составе более длинного идентификатора. Дополнительные сведения см. в разделе токены.
Аргумент строки токена состоит из ряда токенов, таких как ключевые слова, константы или полные инструкции. Один или несколько пробельных символов должны отделять строку токена от идентификатора. Эти пробелы не считаются частью замененного текста, как и все остальные пробелы, следующие за последним токеном текста.
A #define без строки токена удаляет вхождения идентификатора из исходного файла. Идентификатор остается определенным и может быть проверен с помощью #if defined директивы и #ifdef .
Вторая форма синтаксиса определяет макрос, подобный функции, с параметрами. Эта форма допускает использование необязательного списка параметров, которые должны находиться в скобках. После определения макроса каждое последующее вхождение идентификатора(неявноезначение. идентификаторOPT ) заменяется версией аргумента -строки токена , которая содержит фактические аргументы, подставляемые для формальных параметров.
Формальные имена параметров отображаются в строке токена для обозначения мест, в которых заменяются фактические значения. Имя каждого параметра может встречаться несколько раз в строке токена, а имена могут отображаться в любом порядке. Число аргументов в вызове должно соответствовать числу параметров в определении макроса. Надлежащее использование скобок обеспечит правильную обработку сложных фактических аргументов.
Формальные параметры в списке разделяются запятыми. Все имена в списке должны быть уникальными, и список должен быть заключен в скобки. Ни один из пробелов не может разделять идентификатор и открывающую круглую скобку. Использование сцепления строк — перед символом новой строки разместите обратную косую черту ( ) для длинных директив на нескольких исходных строках. Область действия формального имени параметра расширяется до новой строки, в которой заканчивается Строка токена.
Следующие примеры макросов с аргументами иллюстрируют вторую форму синтаксиса #define :
// Macro to define cursor lines #define CURSOR(top, bottom) (((top)
Аргументы с побочными эффектами иногда приводят к тому, что макросы дают непредвиденные результаты. Данный формальный параметр может присутствовать более одного раза в строке токена. Если этот формальный параметр заменяется выражением с побочными эффектами, выражение с такими эффектами может вычисляться несколько раз. (См. примеры в разделе Оператор, посвященный вставлению токена (# #).)
Директива #undef приводит к тому, что определение препроцессора идентификатора забывается. Дополнительные сведения см. в директиве #undef .
Если имя определяемого макроса выполняется в строке токена (даже в результате расширения другого макроса), оно не разворачивается.
Вторая #define макроса с тем же именем создает предупреждение, если вторая последовательность токенов не совпадает с первой.
Блок, относящийся только к системам Microsoft
Если новое определение синтаксически совпадает с исходным, Microsoft C и C++ позволяют переопределить макрос. Другими словами, два определения могут иметь разные имена параметров. Это поведение отличается от ANSI C, которое требует лексического идентичности двух определений.
Например, следующие два макроса идентичны, за исключением имен параметров. ANSI C не допускает такое переопределение, но Microsoft C/C++ компилирует его без ошибок.
#define multiply( f1, f2 ) ( f1 * f2 ) #define multiply( a1, a2 ) ( a1 * a2 )
С другой стороны, следующие два макроса неидентичны и приводят к выдаче предупреждения в Microsoft C и C++.
#define multiply( f1, f2 ) ( f1 * f2 ) #define multiply( a1, a2 ) ( b1 * b2 )
Завершение блока, относящегося только к системам Майкрософт
В этом примере показана директива #define :
#define WIDTH 80 #define LENGTH ( WIDTH + 10 )
Первый оператор определяет идентификатор WIDTH как целочисленную константу 80, а затем LENGTH задается в виде WIDTH и целочисленной константы 10. Каждое вхождение LENGTH заменяется на ( WIDTH + 10 ). В свою очередь, каждое вхождение WIDTH + 10 заменяется выражением ( 80 + 10 ). Скобки вокруг WIDTH + 10 имеют важное значение, поскольку управляют интерпретацией в операторах, например в следующем:
var = LENGTH * 20;
После этапа предварительной обработки этот оператор принимает следующий вид:
var = ( 80 + 10 ) * 20;
что равно 1800. Без скобок результат будет следующим:
var = 80 + 10 * 20;
результатом вычисления которого является 280.
Блок, относящийся только к системам Microsoft
Определение макросов и констант с помощью параметра компилятора /d аналогично использованию директивы предварительной обработки #define в начале файла. С помощью параметра /D можно определить до 30 макросов.
Завершение блока, относящегося только к системам Майкрософт
Источник: learn.microsoft.com
Директивы препроцессора в Си
Препроцессор — это специальная программа, являющаяся частью компилятора языка Си. Она предназначена для предварительной обработки текста программы. Препроцессор позволяет включать в текст программы файлы и вводить макроопределения.
Работа препроцессора осуществляется с помощью специальных директив (указаний). Они отмечаются знаком решетка #. По окончании строк, обозначающих директивы в языке Си, точку с запятой можно не ставить.
Основные директивы препроцессора
#include — вставляет текст из указанного файла
#define — задаёт макроопределение (макрос) или символическую константу
#undef — отменяет предыдущее определение
#if — осуществляет условную компиляцию при истинности константного выражения
#ifdef — осуществляет условную компиляцию при определённости символической константы
#ifndef — осуществляет условную компиляцию при неопределённости символической константы
#else — ветка условной компиляции при ложности выражения
#elif — ветка условной компиляции, образуемая слиянием else и if
#endif — конец ветки условной компиляции
#line — препроцессор изменяет номер текущей строки и имя компилируемого файла
#error — выдача диагностического сообщения
#pragma — действие, зависящее от конкретной реализации компилятора.
Директива #include
Директива #include позволяет включать в текст программы указанный файл. Если заголовочный файл содержит описание библиотечных функций и находится в папке компилятора, он заключается в угловые скобки <> .
Если файл находится в текущем каталоге проекта, он указывается в кавычках «» . Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь.
Источник: prog-cpp.ru