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

Что такое макрос в си

  • автор:

Что такое макрос в си

Все идентификаторы, определяемые с помощью директив #define, которые предполагают замену на определенную последовательность символов, еще называют макросами .

Макросы позволяют определять замену не только для отдельных символов, но и для целых выражений:

#include #define HELLO printf(«Hello World! \n») #define FOR for(int i=0; i

Макрос HELLO определяет вывод на консоль строки «Hello World! \n». А макрос FOR определяет цикл, который отрабатывает 4 раза. И итоге после обработки препроцессора функция main будет выглядеть следующим образом:

int main(void)

То есть данный код 4 раза выведет на консоль строку «Hello World! \n».

Подобные определения директивы #define имеют один недостаток, последовательность символов, которая используется директивой фиксирована. Например, здесь везде, где встретится в исходном коде идентификатор HELLO, выводится строка «Hello World!». Но что, если мы динамически хотим передавать строку, то есть строка может быть любой. В этом случае мы можем задать макроопределение с параметрами в следующей форме:

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

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

Для обращения к макросу применяется конструкция:

имя_макроса(список_аргументов)

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

Например, возьмем банальную операцию, по выводу чисел на консоль и попробуем сократить ее с помощью макросов:

#define print(a) printf("%d \n", a)

Здесь print — это имя макроса или идентификатор, после которого в скобках указан параметр a . Этот параметр будет представлять любое целое число. И любой вызов макроса print будет заменяться на строку printf(«%d \n», a) . Посмотрим, это будет выглядеть на примере:

#include #define print(a) printf(«%d \n», a) int main(void)

Или более сложный пример: определим макрос swap(t,x,y) , который обменивает местами значения двух аргументов типа t :

#include #define t int #define swap(t, x, y) < t temp = x; x = y; y=temp;>int main(void)

Макрос swap применяет блок для обмена значениями. Причем данный макрос фактически универсален: нам неважно, какой тип у переменных x и y.

Или еще один пример — нахождение минимального значения:

#include #define min(a,b) (a < b ? a : b) int main(void) < int x = 23; int y = 14; int z = min(x,y); printf("min = %d", z); // min = 14 return 0; >

То есть в данном случае после работы препроцессора вместо строки int z = min(x,y); мы получим строку:

int z = (x < y ? x : y);

Стоит отметить, что при определении макроса его параметры следует заключать в скобки. Например, возьмем следующую программу:

#include #define SQUARE(n) n*n int main(void) < int x = SQUARE(4+2); printf("x = %d\n", x); // x = 14 return 0; >

Здесь макрос SQUARE принимает один параметр и возводит его в квадрат. Однако при вызове макроса

int x = SQUARE(4+2);

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

int x = 4+2*4+2;

Чтобы получить нужный резульат, поместим параметры макроса в скобки:

#include #define SQUARE(n) (n)*(n) // параметры в скобках int main(void) < int x = SQUARE(4+2); printf("x = %d\n", x); // x = 36 return 0; >

Препроцессорные операции

При обработки исходного кода препроцессор может выполнять две операции: # и ## .

Операция # позволяет заключать текст параметра, который следует после операции, в кавычки:

#include #define print_int(n) printf(#n"=%d \n",n); int main(void) < int x = 23; print_int(x); // x=23 int y = 14; print_int(y); // y=14 int number = 203; print_int(number); // number=203 return 0; >

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

#include #define print(a,b,c) printf("%d", a##b##c); int main(void) < print(2, 81, 34); // 28134 return 0; >

Здесь склеиваются три числа, которые передаются в макрос print. Или аналогичный пример:

#include #define unite(a,b,c) a##b##c; int main(void) < int x = unite(2, 81, 34); // 28134 printf("%d \n", x); return 0; >

Макросы (C/C++)

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

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

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

На практике это означает, что существует два типа макросов. Макросы, подобные объекту, не принимают аргументов. Макросы, подобные функциям, можно определить для принятия аргументов, чтобы они выглядели и действовали как вызовы функций. Так как макросы не создают фактические вызовы функций, иногда можно ускорить выполнение программ, заменив вызовы функций макросами. (В C++встроенные функции часто являются предпочтительным методом.) Однако макросы могут создавать проблемы, если вы не определяете и используете их с осторожностью. Возможно, потребуется использовать круглые скобки в определениях макроса с аргументами, чтобы сохранить правильный приоритет в выражении. Кроме того, макросы могут неправильно обработать выражения с побочными эффектами. Дополнительные сведения см. в getrandom примере директивы #define.

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

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

Дополнительные сведения см. в следующих разделах:

  • Макросы и C++
  • Макросы с переменным числом аргументов
  • Предустановленные макросы

Макросы в C/С++

Макросы - это препроцессорные "функции" , т.е. лексемы, созданные с помощью директивы #define, которые принимают параметры подобно функциям. После директивы #define указывается имя макроса, за которым в скобках (без пробелов) параметры, отделенные запятыми и определение макроса, отделенное пробелом.

#define ADD(x,y) x = x + y

если после этого написать:

int a=2; int b=3; ADD(a,b); cout 

то получим:

a=5 b=3

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

#define MACRO1(x) x * x #define MACRO2(x) ( x ) * ( x ) int a =2; int b=3; cout 

результат:

macro 1- 11 macro 2- 25

В первом случае получили 2+3*2+3, то есть 2+6+3=11, а во втором (2+3)*(2+3), т.е. 5*5=25.

Для управления строками в макросах сучествует 2 оператора - оператор взятия в кавычки(#) и оператор контактенации (##). Оператор # берет в кавычки следуючие за ним символы до очередного пробела, например, если объявить так:

#define PRINT(x) cout 

то

PRINT( a string ); равносильно cout 

Оператор ## используется для контактенации строк, то есть "склеивания" нескольких строк в одну. Например, у вас есть несколько функций с именами MyFirstFunction(), MySecondFunction(), MyThirdFunction() и т.д. Для их вызова можно определить макрос:

#define CALLFUNC(x) My##x##Function()

и вызывать функции MyFirstFunction(), MySecondFunction() ,MyThirdFunction() и т.д. макросом CALLFUNC

CALLFUNC(First); CALLFUNC(Second);

У многих компиляторов есть ряд встроенных макросов. Наиболее распостраненные - __DATE__ , __TIME__ , __LINE__ , __FILE__ , которые заменяются текущей (на время компиляции) датой, временем, номером строки и именем исходного файла соответственно. Встроенные макросы можно использовать без объявления. Пример:

cout 

Результат:

compiled on Sep 52001 23:49:55

Макросы

П еред тем как программа будет скомпилирована (или не будет, если найдены ошибки), текст программы обрабатывается препроцессором. Препроцессор позволяет изменять текст программы, используя специальные директивы.
Директива #define определяет новый макрос. Макрос, или макроподстановка, будет заменена в коде программы своим телом. Например, мы часто пользовались макросом

#define SIZE 20

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

#define BREAK_WORD "end"

Макрос подставляется непосредственно в текст вашей программы. То есть, если у вас был код

#include #include #define LENGTH 128 void main()

то он будет заменён на код

#include #include void main()

Иными словами, макроподстановка - это просто подмена одного куска текста на другой.

Макросы могут иметь аргументы.

#define MAX (a, b) a > b ? a: b

#include #include #define MAX(x, y) x > y ? x: y void main()

Несмотря на то, что этот код работает, в нём есть ошибки. Макроподстановка – это именно подстановка:

#include #include #define MAX(x, y) x > y ? x: y void main()

Будет выведено
max number is 11
a = 11
b = 12 Это связано с тем, что код будет подменён следующим образом ("max number is %d\n", a++ > b++ ? a++: b++);
В данном случае возвращаемое значение будет ещё раз инкрементировано. Теперь рассмотрим макрос

#include #include #define SPHERE_VOLUME(r) 4,18879020 * (r) * (r) * (r) void main()

С одной стороны, этот макрос должен делать программу быстрее, если заменить им вызов функции. Но на деле работать он будет медленнее. Макрос развернётся в следующий код
4,18879020 * (halfA + halfB) * (halfA + halfB) * (halfA + halfB)
итого, три раза будет вызвано сложение. Вот ещё пример ошибки

#include #include #define MUL(x, y) x * y void main()

В данном случае будет выведено 19 вместо 45, так как макрос будет раскрыт в выражение
2 + 3 * 4 + 5 == 2 + 12 + 5 == 19
Решением будет следующий макрос:

#include #include #define MUL(x, y) (x) * (y) void main()

И ещё одна ошибка, которая также встречается очень часто. Давайте напишем макрос, который будет выводить на печать массив. Мы воспользуемся им в сортировке пузырьком, чтобы видеть, как изменяется массив во время сортировки.

#include #include #define DISPLAY_ARRAY(arr, size) \ for (i = 0; i < size; i++) \ printf("\n"); #define SIZE 10 void main() < int a[SIZE] = ; int tmp; char flag, i; do < flag = 0; //Проходим по массиву. Если следующий элемент больше предыдущего, то //меняем их местами и по новой проверяем массив for (i = 1; i < SIZE; i++) < if (a[i] >a[i - 1]) < tmp = a[i]; a[i] = a[i - 1]; a[i - 1] = tmp; flag = 1; >DISPLAY_ARRAY(a, SIZE); > > while(flag); getch(); >

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

#include #include #define DISPLAY_ARRAY(arr, size) \ printf("\n");\ > #define SIZE 10 void main() < int a[SIZE] = ; int tmp; char flag, i; do < flag = 0; //Проходим по массиву. Если следующий элемент больше предыдущего, то //меняем их местами и по новой проверяем массив for (i = 1; i < SIZE; i++) < if (a[i] >a[i - 1]) < tmp = a[i]; a[i] = a[i - 1]; a[i - 1] = tmp; flag = 1; >DISPLAY_ARRAY(a, SIZE); > > while(flag); getch(); >
  • 1. Всегда окружайте параметры круглыми скобками
  • 2. Старайтесь передавать параметры явно и не передавать выражения, которые должны быть вычислены. Это будет приводить к неявным побочным эффектам и замедлению работы за счёт повторного выполнения кода.
  • 3. Тело сложного макроса заносите под фигурные скобки.

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

int main(void)

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

#define SOME_TEXT "print number" #define PRINT_INT "%d" int main(void)

Условные конструкции

#ifdef #else #endif

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

#include #include #define DEBUG #ifdef DEBUG #define info(msg) printf("%s\n", msg) #else #define info(msg) #endif void main() < unsigned int bound, i, sum = 0; scanf("%d", &bound); info("step 1 finished"); for (i = 0; i < bound; i++) < sum += i; >info("step 2 finished"); printf("%d", sum); getch(); >

Если теперь ввести 12, то программа выведет
step 1 finished
step 2 finished
66
Если же удалить строку #define DEBUG , то будет выведено только
66
потому что сработает вторая ветвь условия и info("строка") будет заменено на пустую строку.
Изменим макрос следующим образом

#define ON 1 #define OFF 0 #define DEBUG ON #if DEBUG == ON #define info(msg) printf("%s\n", msg) #else #define info(msg) #endif

Теперь вместо ifdef мы использовали директиву if , она в зависимости от условия выбирает первую или вторую ветвь. Также мы использовали макрос ON и OFF, а в дальнейшем использовали этот макрос в другом макросе. Это возможно, потому что первый макрос заменяется далее по ходу программы на своё тело. Так что первый макрос изменяет остальные макросы, а потом они уже вставляются далее в программу.
В этом примере, для того, чтобы отключить вывод сообщений, достаточно поменять строчку

#define DEBUG ON
#define DEBUG OFF

Кроме директивы #ifdef используется директива #ifndef (if not defined), он работает также, но первая ветвь работает только в случае, если макрос не определён. Также, как и с условными конструкциями, макрос может и не содержать ветви else.

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

  • __LINE__ - заменяется на текущую строку, в которой встречается этот макрос. Очень удобно для отлова ошибок – всегда можно возвращать не только сообщение об ошибке, но сразу же и номер строки.
  • __FILE__ - имя текущего файла. Также очень удобно, в том случае, если программа состоит из множества файлов.
  • __DATE__ - дата трансляции файла в формате Mmm dd yyyy . Если дата трансляции не может быть получена, то будет выведена какая-то действительная дата, в зависимости от реализации.
  • __TIME__ - время трансляции файла в формате hh:mm:ss . Если время трансляции не может быть получено, то будет выведено какое-то действительное время, в зависимости от реализации.
  • __STDC__ - макрос определён, если программа была откомпилирована с использованием стандарта ANSI С со включенной проверкой на совместимость. В противном случае __STDC__ не определен
#define ON 1 #define OFF 0 #define DEBUG ON #if DEBUG == ON #define err(msg) printf("Error in %s at line %d: %s\n", __FILE__, __LINE__, msg) #else #define err(msg) #endif

Использование препроцессора для инициализации объектов

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

#include int main() < int a[] = < #include "./array.txt"; >; size_t i; for (i = 0; i < 10; i++) < printf("%d\n", a[i]); >_getch(); return 0; >

array.txt в той же директории

1, 2, 3, 4, 5, 6, 7, 8, 9, 10

Макросы с переменным числом параметров

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

typedef struct Node < int value; struct Node *next; >Node; void push(Node **head, int data) < Node *tmp = (Node*) malloc(sizeof(Node)); tmp->value = data; tmp->next = (*head); (*head) = tmp; > int pop(Node **head) < Node* prev = NULL; int val; if (head == NULL) < exit(-1); >prev = (*head); val = prev->value; (*head) = (*head)->next; free(prev); return val; > void fromArray(Node **head, int *arr, size_t size) < size_t i = size - 1; if (arr == NULL || size == 0) < return; >do < push(head, arr[i]); >while(i--!=0); >

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

Функция fromArray получает три аргумента – указатель на узел, массив и его размер. Мы хотим избавиться от размера и массива. Тем не менее, всё равно придётся передавать тип массива, чтобы автоматически можно было изменять его размер.

#define fromArr(list, type, . ) ;\ fromArray(&list, xname, (sizeof(xname)/sizeof(type)));\ >

Макрос принимает два обязательных параметра – имя узла и название типа. Оставшихся параметров будет произвольное число, они перечисляются через запятую.

type xname[] = ;\

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

fromArray(&list, xname, (sizeof(xname)/sizeof(type)));\

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

Node *head = NULL; fromArr(head, int, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);

таким образом, будет трансформирован в

< int xname[] = ; fromArray(&head, xname, (sizeof(xname)/sizeof(int))); >

Стрингизация и конкатенация макросов

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

typedef struct command_tag < const char *name; void (*function) (void); >command_t; command_t commands[] = < < "quit", quit_command >, < "init", init_command >>;

Для сокращения кода можно объявить такой макрос

#define COMMAND(NAME)

Здесь #NAME превращает переданный параметр в строку, а NAME ## _command конкатенирует параметр с _command. Весь код:

#include #define COMMAND(NAME) < #NAME, NAME ## _command >void quit_command() < printf("I am a quit command\n"); >void init_command() < printf("I am a init command\n"); >typedef struct command_tag < const char *name; void (*function) (void); >command_t; #define SIZE 2 command_t commands[] = < COMMAND(quit), COMMAND(init), >; int main() < size_t i; for (i = 0; i < SIZE; i++) < printf("%s says ", commands[i].name); commands[i].function(); >_getch(); return 0; >

Другой пример - макрос, который выводит на печать макрос.

#include #define STR(X) #X #define PRINT_MACROS(X) printf("%s", STR(X)) #define EXAMPLE __somedata int main()

Этот макрос выведет на печать __somedata

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

ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

email

Всё ещё не понятно? – пиши вопросы на ящик

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

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