Директива #define (C/C++)
#define создает макрос, который является связью идентификатора или параметризованного идентификатора со строкой токена. После определения макроса компилятор может подставить строку токена для каждого обнаруженного идентификатора в исходном файле.
Синтаксис
#define маркер-строкаидентификатора
#define идентификатор (идентификатор opt, . ,identifier opt)token-string opt
Замечания
Директива #define приводит компилятору заменить строку маркера для каждого вхождения идентификатора в исходном файле. Идентификатор заменяется только в том случае, если он формирует маркер. То есть идентификатор не заменяется, если он отображается в комментарии, в строке или в составе более длинного идентификатора. Дополнительные сведения см. в разделе «Токены».
Аргумент строки токена состоит из ряда маркеров, таких как ключевое слово, константы или полные операторы. Один или несколько символов пробелов должны отделять строку маркера от идентификатора. Эти пробелы не считаются частью замененного текста, как и все остальные пробелы, следующие за последним токеном текста.
Без #define строки маркера удаляется вхождения идентификатора из исходного файла. Идентификатор остается определенным и может быть проверен с помощью #if defined директив и #ifdef инструкций.
Вторая форма синтаксиса определяет макрос, подобный функции, с параметрами. Эта форма допускает использование необязательного списка параметров, которые должны находиться в скобках. После определения макроса каждое последующее вхождение идентификатора (opt , . идентификатор opt) заменяется версией аргумента строки токена, который имеет фактические аргументы, заменяемые формальными параметрами.
Имена формальных параметров отображаются в строке токена, чтобы пометить расположения, в которых заменяются фактические значения. Каждое имя параметра может отображаться несколько раз в строке маркера, и имена могут отображаться в любом порядке. Число аргументов в вызове должно соответствовать числу параметров в определении макроса. Надлежащее использование скобок обеспечит правильную обработку сложных фактических аргументов.
Формальные параметры в списке разделяются запятыми. Все имена в списке должны быть уникальными, и список должен быть заключен в скобки. Пробелы не могут разделять идентификатор и открываемую скобку. Используйте объединение строк — поместите обратную косую косую черту ( \ ) непосредственно перед новым символом — для длинных директив в нескольких исходных строках. Область формального имени параметра распространяется на новую строку, которая заканчивается строкой токена.
Если макрос определен во второй форме синтаксиса, последующие текстовые экземпляры, за которыми находится список аргументов, указывают на вызов макроса. Фактические аргументы, следующие за экземпляром идентификатора в исходном файле, соответствуют соответствующим формальным параметрам в определении макроса. Каждый формальный параметр в строке токена, который не предшествует строковой (), charizing ( #@ # ), или оператор вставки маркеров ( ## ) или не следует ## оператору, заменяется соответствующим фактическим аргументом. Перед заменой директивой формального параметра все макросы в фактическом аргументе разворачиваются. (Операторы описаны в разделе Операторы препроцессора.)
В следующих примерах макросов с аргументами показана вторая форма синтаксиса #define :
// Macro to define cursor lines #define CURSOR(top, bottom) (((top)
Аргументы с побочными эффектами иногда приводят к тому, что макросы дают непредвиденные результаты. Заданный формальный параметр может отображаться несколько раз в строке токена. Если этот формальный параметр заменяется выражением с побочными эффектами, выражение с такими эффектами может вычисляться несколько раз. (См. примеры в разделе Оператор вставки токенов (#).)
Директива #undef приводит к тому, что определение препроцессора идентификатора забывается. Дополнительные сведения см . в директиве #undef.
Если имя определяемого макроса происходит в строке маркера (даже в результате другого расширения макроса), он не расширяется.
Второй #define для макроса с тем же именем создает предупреждение, если только вторая последовательность маркеров не идентична первой.
Блок, относящийся только к системам Майкрософт
Если новое определение синтаксически совпадает с исходным, 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;
Блок, относящийся только к системам Майкрософт
Определение макросов и констант с параметром компилятора /D имеет тот же эффект, что и при использовании директивы предварительной обработки #define в начале файла. С помощью параметра /D можно определить до 30 макросов.
Завершение блока, относящегося только к системам Майкрософт
#define CPP WTF

Уже давным-давно я работал в одной крупной компании в должности C++-разработчика и столкнулся с одной очень странной ошибкой. Я написал примерно такой класс:
class Foo < static void* operator new() < return . ; >; >;
И увидел огромный stack-trace ошибок о недопустимом вызове оператора в этом контекста (на тот момент я использовал MS Visual Studio 2013 и встроенный в него MSVC-компилятор). Я искал проблему часа два, и помогло мне только просматривание готовой единицы трансляции. Как вы могли догадаться, проблема была связана с препроцессором, но обо всём по порядку.
Препроцессор в C++ — это такая, на первый взгляд, очень простая штука, основное назначение которой — добавлять в ваш исходный файл куски кода до того, как им займётся компилятор (для знающих: препроцессор формирует единицы трансляции). Он подключает include-файлы, обрабатывает всякие там #pragma once , но самая главная директива препроцессора, безусловно, это директива #define . Она позволяет заменять один кусок текста на другой. Например, вы пишете #define foo goo , и после этого на этапе работы препроцессора все упоминания отдельного токена (слова) foo в вашем коде заменяются на goo .
Это очень мощный инструмент, он поддерживает аргументы и даже раскрывает последовательности (правда, без рекурсии):
#define foo goo #define goo doo foo(); // тут вызовется doo
Единственная проблема — препроцессор очень тупой. Он совершенно не следит за тем, что именно вы define'ите. Например, можно сделать так: #define float double , и это действительно заменит все float на double ; или так: #define std qwerty , и это заменит все упоминания пространства имён std на qwerty ( std::cout -> qwerty::cout ).
Препроцессор действует на уровне единицы трансляции, то есть #define , объявленный в файле a.cpp, не будет действовать в файле b.cpp. Но там, где он действует, он будет действовать беспощадно 🙂 #define , которого вы не ожидаете может очень сильно изменить логику работы программы и неприятно удивить. Ровно так и получилось в моём случае: в одном из include-файлов было такое объявление:
#define new DEBUG_NEW
а в другом include-файле, такое:
#define DEBUG_NEW new(__LINE__, __FILE__)
Что сделал наш волшебный препроцессор? Правильно, честно всё заменил, и в результате получилось вот так:
class Foo < static void* operator new(__LINE__, __FILE__)() < return . ; >; >;
Для чего нужен DEBUG_NEW — вопрос отдельный. Коротко говоря, это отладочный оператор выделения памяти, ведущий учёт всех запрошенных блоков. Тем не менее, теперь ошибка синтаксиса языка уже становится очевидной. Однако это не отменяет того факта, что искать её было очень тяжело.
Успешной вам отладки 🙂
Макросы в С и С++

Макросы - один из моих самых любимых инструментов в языках С и С++. Умные люди и умные книжки советуют по максимуму избегать использования макросов, по возможности заменяя их шаблонами, константами и inline-функциями, и на то есть веские основания. С помощью макросов можно создавать не только изящный код, но и плодить не менее изящные баги, которые потом будет очень сложно отловить и пофиксить. Но если соблюдать ряд несложных правил при работе с макросами, они становятся мощным оружием, которое не стреляет по твоим собственным коленям. Но сперва разберемся, что вообще такое макросы в С и С++?
Что есть макросы?
В языках С и С++ есть такой механизм, как препроцессор. Он обрабатывает исходный код программы ДО того, как она будет скомпилирована. У препроцессора есть свои директивы, такие как #include, #pragma, #if и тд. Но нам интересн/а только директива #define.
В языке Си довольно распространенной практикой является объявление глобальных констант с помощью директивы #define:
#define PI 3.14159
А потом, на этапе препроцессинга, все использования PI будут заменены указанным в объявлении макроса токеном:
double area = 2 * PI * r * r;
После препроцессинга, который по сути является банальной подстановкой, это выражение превратится в:
double area = 2 * 3.14159 * r * r;
PI - макрос, в самом простом его исполнении. Естественно, макросы в таком виде не работают как переменные. Им нельзя присваивать новое значение или использовать их адрес.
// Так нельзя: PI = 3; // после препроцессинга: 3.14159 = 3 int *x = &PI; // после препроцессинга: int *x = &3.14159
О макросах важно понимать, что область видимости у них такая же, как у нестатических функций в языке Си, то есть они видны везде, куда их "заинклюдили". Однако в отличии от функций, объявление макроса можно отменить:
#undef PI
После этой строчки обращаться к PI будет уже нельзя.
Макросы с параметрами
Самое интересное начинается, когда у макросов появляются параметры. Параметры в макросах работают примерно так же, как аргументы функции. Простой пример - макрос, который определяет больший из переданных ему параметров:
#define MAX(a, b) a >= b ? a : b
Макрос может состоять не только из одного выражения. Например макрос, который меняет значения двух переменных:
#define SWAP(type, a, b) type tmp = a; a = b; b = tmp;
Поскольку мы первым параметром передаем тип, данный макрос будет работать с переменными любого типа:
SWAP(int, num1, num2) SWAP(float, num1, num2)
Однако использовать данный макрос так, как показано выше (один за другим) не получится, потому что в результате макроподстановки у нас окажется два объявления переменной tmp с разными типами. Решить эту проблему позволяет такая синтаксическая конструкция, как statement expression:
#define SWAP(type, a, b) (< type tmp = a; a = b; b = tmp; >)
С помощью нее мы можем задать область видимости для макроса и безопасно объявлять переменные внутри. Но стоит помнить, что данная конструкция не входит в стандарт Си, а является GNU расширением языка. Она поддерживается компиляторами gcc и clang.
Также в подобных макросах, вместо передачи типа аргументов первым параметром, полезно использовать оператор typeof в языке C или decltype в C++. С их помощью можно удобно объявлять переменную tmp того же типа, что и переданные аргументы:
#define SWAP(a, b) (< decltype(a) tmp = a; a = b; b = tmp; >)
Макросы также можно записывать в несколько строк, но тогда каждая строка, кроме последней, должна заканчиваться символом '\':
#define SWAP(a, b) (< \ decltype(a) tmp = a; \ a = b; \ b = tmp; >)
Параметр макроса можно превратить в строку, добавив перед ним знак '#':
#define PRINT_VALUE(value) printf("Value of %s is %d", #value, value); int x = 5; PRINT_VALUE(x) // -> Value of x is 5
А еще параметр можно приклеить к чему-то еще, чтобы получился новый идентификатор. Для этого между параметром и тем, с чем мы его склеиваем, нужно поставить '##':
#define PRINT_VALUE (number) printf("%d", value_##number); int value_one = 10, value_two = 20; PRINT_VALUE(one) // -> 10 PRINT_VALUE(two) // -> 20
Техника безопасности при работе с макросами
Есть несколько основных правил, которые нужно соблюдать при работе с макросами.
1. Параметрами макросов не должны быть выражения и вызовы функций.
Ранее я уже объявлял макрос MAX. Но что получится, если попытаться вызвать его вот так:
int x = 1, y = 5; int max = MAX(++x, --y);
Со стороны все выглядит нормально, но вот что получится в результате макроподстановки:
int max = ++x >= --y ? ++x : --y;
В итоге переменная max будет равна не 4, как мы ожидали, а 3. Потом можно уйму времени потратить, отлавливая эту ошибку. Так что в качестве аргумента макроса нужно всегда передавать уже конечное значение, а не какое-то выражение или вызов функции. Иначе выражение или функция будут вычислены столько раз, сколько используется этот параметр в теле макроса. Эту проблему также можно решить с помощью описанного выше подхода со statement expression:
#define MAX(a,b) ( _b ? _a : _b; >)
Использование временных переменных _a и _b в данном случае позволяет нам избежать side effect'ов параметров макроса MAX.
2. Все аргументы макроса и сам макрос должны быть заключены в скобки.
Это правило я уже нарушил при написании макроса MAX. Что получится, если мы захотим использовать этот макрос в составе какого-то математического выражения?
int result = 5 + MAX(1, 4);
По логике, переменная result должна будет иметь значение 9, однако вот что мы получаем в результате макроподстановки:
int result = 5 + 1 > 4 ? 1 : 4;
И переменная result внезапно примет значение 1. Чтобы такого не происходило, макрос MAX должен быть объявлен следующим образом:
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
В таком случае все действия произойдут в нужном порядке.
3. Многострочные макросы должны иметь свою область видимости.
Например у нас есть макрос, который вызывает две функции:
#define MACRO() doSomething(); \ doSomethinElse();
А теперь попробуем использовать этот макрос в таком контексте:
if (some_condition) MACRO()
После макроподстановки мы увидим вот такую картину:
if (some_condition) doSomething(); doSomethinElse();
Нетрудно заметить, что под действие if попадет только первая функция, а вторая будет вызываться всегда. Именно для того, чтобы избежать подобных багов, у макросов должна быть объявлена своя область видимости. Для удобства в этих целях принято использовать цикл do-while.
#define MACRO() do < \ doSomething(); \ doSomethingElse(); \ >while(0)
Поскольку в условии цикла стоит ноль, он отработает ровно один раз. Это делается, во первых, для того, чтобы у тела макроса появилась своя область видимости, ограниченная телом цикла, а во вторых, чтобы сделать вызов макроса более привычным, потому что теперь после MACRO() нужно будет обязательно ставить точку с запятой. Если бы мы просто ограничили тело макроса фигурными скобками, точку с запятой после его вызова поставить бы не получилось.
if (some_condition) MACRO();
Еще немного примеров
В языке Си при помощи макросов можно эффективно избавляться от дублирования кода. Банальный пример - объявим несколько функций сложения для работы с разными типами данных:
#define DEF_SUM(type) type sum_##type (type a, type b)
Теперь чтобы нагенерировать таких функций для нужных нам типов, нужно просто использовать пару раз этот макрос в глобальной зоне видимости:
DEF_SUM(int) DEF_SUM(float) DEF_SUM(double) int main()
Таким образом у нас получился аналог шаблонов из С++. Но стоит сразу обратить внимание, что данный способ не подойдет для типов, название которых состоит более чем из одного слова, например long long или unsigned short, потому что не получится нормально склеить название функции (sum_##type). Для этого сперва придется объявить для них новый тип, состоящий из одного слова.
В современном С++ можно спокойно обходиться без макросов вовсе, используя только шаблоны и inline-функции. Но в Си жить с макросами все же удобнее, чем без них. При грамотном использовании макросы позволяют избавиться от большого количества дублирования кода и сделать сам код более симпатичным и удобочитаемым.
C define как в c
Директива #define определяет идентификатор и последовательность символов, которые будут подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Формальное определение директивы:
#define идентификатор последовательность_символов
Используем директиву #define:
#include #define N 23 int main(void) < int x = N; printf("Number: %d", x); // Number: 23 return 0; >
Здесь определен один идентификатор N. В программе, где встречается этот идентификатор, он будет заменяться на число 23. Например, строка
int x = N;
после обработки препроцессором будет иметь следующий код
int x =23;
Более сложный пример
#include #define BEGIN < #define END >#define N 23 int main(void) BEGIN int x = N; printf("Number: %d", x); // Number: 23 return 0; END
Здесь определены три идентификатора BEGIN, END, N. В итоге все вхождения последовательности символов "BEGIN" будут заменяться на открывающую фигурную скобку, а "END" - на закрывающую, а символ "N" на число 23.
Таким образом, после обработки препроцессора функция main приобретет следующий вид:
int main(void)
#define также может определять более сложные выражения. Например:
#include #define ADD(a,b) (a+b) int main(void) < int n1 = 10; int n2 = 5; printf("%d + %d = %d", n1, n2, ADD(n1, n2)); // 10 + 5 = 15 >
В данном случае выражение ADD(a,b) будет заменяться операцией сложения двух чисел (a + b)
Особенно удобно использовать директиву #define для определения размеров массивов:
#include #define N 4 int main(void) < int numbers[N] = ; for(int i=0; i return 0; >
В данном случае если мы захотим глобально поменять размер массива, то достаточно изменить значение N в директиве define.
Следует учитывать, что директива препроцессор не заменяет последовательности символов в двойных и одинарных кавычках и в комментариях:
#include #define N 4 int main(void) < char symbol = 'N'; printf("%c \n", symbol); // N printf("N"); //N return 0; >
Причем если идентификатор должен представлять одно слово, то его последовательность символов может состоять из нескольких слов или символов, разделенных пробелами:
#define REAL long double
Директива #undef
В процессе работы мы можем многократно определять новое значение для одного идентификатора:
#define N 23 #define N 32 #define N 55
Но некоторые компиляторы, в частности, gcc, могут выдавать предупреждения при повторном определении идентификатора, и чтобы выйти из этой ситуации, мы можем использовать директиву #undef для отмены действия макроса. Эта директива имеет следующее определение:
#undef идентификатор
#include #define STRING "Good morning \n" int main(void)