Препроцессор – это программа, вызываемая компилятором языков С и C++ перед процессом трансляции, которая выполняет команды, начинающиеся с #
, делает замены в тексте программы, заданные макросами, и удаляет комментарии.
Макросы могут быть двух видов:#define
имя – простая замена#define
"имя"("параметры") quad "текст" – замена с параметрами
Первый вариант подставляет указанный текст вместо имени. Определяемое таким образом имя нельзя использовать как идентификатор, оно становится "резервированным", поэтому рекомендуется писать имена макросов полностью прописными буквами.
#define SIZE 100
#define size 10
int main() {
int a[SIZE]; // OK
int size; // Error
}
Длинный текст подстановки можно разделить на несколько строк, указав символ \
в конце строки:
#define ABC aaa\
bbb\
ccc
Некоторые макросы уже определены:
__STDC_VERSION__
– поддерживаемая версия языка при компиляции как С__cplusplus
– поддерживаемая версия языка при компиляции как С++__FILE__
– имя текущего файла в кавычках__LINE__
– номер текущей строки__DATE__
– текущая дата в кавычках в формате "Mmm dd yyyy"__TIME__
– текущее время в кавычках в формате "hh:mm:ss"Второй вариант подставляет вместо имени, после которого указаны аргументы в круглых скобках, указанный текст, при этом имена параметров в тексте заменяются на соответствующие аргументы. Аргументы не вычисляются, в отличие от аргументов функции, поэтому при подстановке параметры нужно брать в скобки.
#include <stdio.h>
#define SQR1(x) x*x
#define SQR2(x) ((x)*(x))
int main() {
int s1=100/SQR1(2+3); // Error: 100/2+3*2+3
int s2=100/SQR2(2+3); // OK: 100/((2+3)*(2+3))
int a=5;
int s3=SQR2(++a); // Error: ((++a)*(++a))
printf("%d %d %d\n",s1,s2,s3);
}
Если нужно вставить аргумент в текст, в котором до или после места вставки находятся буквы или цифры, имя другого параметра, то до или после имени параметра нужно написать ##
. Таким образом можно создавать идентификаторы, используя аргументы макроса.
#define A(i) a##i
#define SIZE(x) x##_size
#define MAX_SIZE(x) (sizeof(x)/sizeof(x[0]))
int a1,a2,a3,a4,a5;
double arr[100]; // массив
int arr_size=5; // текущий размер массива
...
A(1)=10; // a1=10;
A(2)=A(1);
for(int i=0; i<SIZE(arr); ++i) arr[i]=0;
Параметры не подставляются в текстовых константах. Для создания строки из аргумента
в тексте подстановки перед именем параметра нужно указать символ #
. Строковая константа разбивается при этом на части, каждая часть записывается в отдельных кавычках, #
"параметр" записывается вместо одной из частей. Для получения символьной константы можно взять 0-й символ из строковой: (#
"параметр" [0])
.
Пример макроса для отладочной печати: печатаются не только значения, но и сами выражения; выбор нужного формата выполняется с помощью операции "выбора по типу".
#include <stdio.h>
#define PRINT(x) printf(_Generic(x, int: #x"=%d\n",\
double: #x"=%lg\n",\
char*: #x"=%s\n",\
default: #x"=?\n"),x);
int main() {
int a=10;
double d=2.5;
char text[]="Hello";
PRINT(a+5);
PRINT(2*d);
PRINT(text);
PRINT(text[1]);
PRINT(a+5LL);
}
Если в тексте подстановки встречается уже использованный макрос, то повторно он не применяется (рекурсивная подстановка невозможна).
#define A A a
#define F(x) x*F(x)
...
F(A) // -> A*F(A) -> A a*F(A a)
У макроса может быть переменное количество аргументов, в этом случае после первых фиксированных параметров нужно указать … как у функций, в тексте подстановки для получения этих аргументов использовать __VA_ARGS__
. Вспомогательная конструкция __VA_OPT__(
s )
добавляет в подстановку текст s, если список аргументов не пустой.
Пример макроса, который выполняет отладочную печать от 1 до 5 значений:
#define PRINT_ALL(x, ...) PRINT(x) __VA_OPT__(; PRINT2(__VA_ARGS__))
#define PRINT2(x, ...) PRINT(x) __VA_OPT__(; PRINT3(__VA_ARGS__))
#define PRINT3(x, ...) PRINT(x) __VA_OPT__(; PRINT45(__VA_ARGS__))
#define PRINT45(x, ...) PRINT(x) __VA_OPT__(; PRINT(__VA_ARGS__))
...
PRINT_ALL(a,b,c*5);
PRINT_ALL(arr[1],arr[2]);
Команда#undef
"имя"
позволяет убрать макрос, чтобы он стал доступным в качестве обычного идентификатора или можно было задать другой макрос.