Макросы на примерах

Макросы в C

Как писать макросы? Как сделать макрос? Как создавать макросы? Где найти информацию по макросам на русском? Такие вопросы – нередкость. Эта статья для начинающих, её можно было бы назвать «макросы для чайников» или «простой учебник по макросам»

Прямо сейчас разберемся, как написать макрос и рассмотрим примеры макросов. Но прежде ответим на естественный вопрос: зачем нужны макросы?

Для чего нужны макросы?

Для чего нужны макросы? Речь идёт о макросах в C. Макросы позволяют упрощать программу: вместо фразы можно указать её идентификатор. Есть макросы, подобные функциям, текст такой «функции» подставляется на этапе предварительной обработки в место её вызова, что потенциально повышает скорость выполнения программы, ведь нет вызова функции. Эти действия выполняются препроцессором. Есть и другие плюсы работы с макросами, но есть и минусы, о чём будет сказано ниже, когда мы перейдём к примерам создания макросов.

#define

#define в C — это директива, которая применяется при определении символических констант, идентификаторов, макрофункций. Макросы, по-английски macro, бывают двух типов: подобные функциям и подобные объектам. В нашем самоучителе по макросам мы рассмотрим работу с этими типами макросов.

Пример макроса

Пример для макроса-объекта. Это определение символической константы:

Здесь определена символическая константа

#define SBP «SBP-Program»

SBP — это идентификатор,
«SBP-Program» — это строка, которая будет подставлена вместо SBP.

Получаем: символическая константа

Строка

#define SBP «SBP-Program»

это макроопределение.

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

Определяем идентификатор:

#define IDENTIFIER_EXAMPLE

Вместо него в программе будет подставлен пробел.

Получаем: макросы

Если строка-подстановка не помещается в строку файла, то применяем обратную косую черту в качестве знака переноса. Пример:

Получаем: макросы

Отметим, что макросы, подобные объектам, не принимают параметры.

Далее рассмотрим пример макроса-функции.

Пример макроса-функции

Рассмотрим простой пример макроса, подобного функции, такие макросы ещё называют макрофункциями:

Получаем: макроопределение

Строка

#define mCircleLength(r) (6.28318 * (r))

это макроопределение. И что здесь определено? mCircleLength — это название макроса. Префикс m подсказывает нам, что это именно макрос, а не обычная функция. Макрос mCircleLength подобен функции, он принимает параметр r.

Напомню, что макросы, подобные объектам, не принимают параметров.

Что вычилсяет макрос mCircleLength? Он вычисляет длину окружности. Длина окружности равна 2Пи умножить на радиус. 2Пи = 6.28318.

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

printf(«nCircle length = %.2fnn», (6.28318 * (12)));

После имени макроса в макроопределении, перед первой открывающей скобкой не должно быть пробела. Это важно. Если пробел будет, то мы получим определение символической константы.

Рассмотрим тело макроса

(6.28318 * (r))

оно охвачено круглыми скобками. Это существенно в общем случае. Параметр r также в скобках. И это важно.

Вот примеры того, что может случиться, если пренебречь скобками:

#define mSum(param) param + 2

int nVar = 10 * mSum(5);

после подстановки тела макроса получим:

int nVar = 10 * 5 + 2;

nVar будет хранить число 52, а не 70, как мы ожидали.

Теперь рассмотрим последствия отсутствия скобок вокруг параметра. Если в наш макрос mCircleLength будет передан параметр в виде суммы, например, 1 + 2, то получим:

#define mCircleLength(r) (6.28318 * r)

(6.28318 * 1 + 2)

Вывод: скобки во многих случаях нужны.

Как с помощью макроса превратить объект в строку?

Вот пример такого макроса:

#define mIntToStr(nVar) (#nVar)

в этой макрофункции строковый оператор (так его иногда называют) # подействует на параметр nVar, в результате nVar будет охвачен кавычками. Код:

Получаем: IntToStr

оператор # охватил параметр 1 + 2 кавычками, превратив его в строковый литерал. Если убрать # в макроопределении, то макрос mIntToStr сложит 1 + 2. Пример макроса:

Получаем: C sizeof

Как с помощью макроса объединять строки?

Строки в C можно объединить с помощью оператора ##. Пример макроса:

Получаем: Concat

Указатели в макросах

В макросах можно использовать указатели. Пример макроса с указателями:

Получаем: Concat

Обратите внимание на расстановку скобок в теле макрофункции.

Локальные переменные в макросах

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

Получаем: Локальные переменные в макросах

Блоки в макросах

В макросах можно применять фигурные скобки, т.о. получать блок. Пример макроса:

Получаем: Блоки в макросах

Двойные вычисления в макросах

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

Получаем: Блоки в макросах

Почему такой результат? Инкремент ++nVar рассчитался дважды. Лучше избегать использования таких решений.

Точка с запятой в макросах

В Си в конце выражения мы ставим точку с запятой

int nVar = 5;

Нужно ли это делать и в макросах? Зависит от конкретного случая. Выше, в разделе «Блоки в макросах» имеется макрофункция:

#define mSum(pVar) {(*(pVar)) += 10;}

Если убрать точку с запятой после 10 в этом примере (см. весь пример), то компилятор выдаст ошибку.

В других случаях наличие точки с запятой ведёт к ошибке. Пример (этот пример содержит ошибку):

В этом примере макрос развернётся так:

if(nVar > 0)
  nVar = 10;
  int nNewVar = 1;
else
  nVar = 0;

Здесь потерялась связь между if и else.

Выход следующий:

#define mList do{nVar = 10; int nNewVar = 1;}while(0)

В этом случае макрос развернётся так:

Последний вариант работает нормально. Код:

Получаем: Точка с запятой в макросах

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

Макросы в C могут иметь переменное число параметров. Такие макросы называют variadic macros. Типичный пример variadic macro:

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

Правила здесь такие:

  • переменные параметры обозначаем тремя точками;
  • если имеются и именованные параметры. То переменные параметры должны быть последними в списке параметров
  • в теле макроса переменные параметры заменяют идентификатором __VA_ARGS__

Теперь пример макроса с переменным числом параметров и с именованными параметрами:

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

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

mMyPrint(szFormat);

в этом случае макрос развернётся в

printf(szFormat, );

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

Запуск из макроса другого макроса

Запуск макроса из другого макроса рассмотрим на простом примере:

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

Вот такой результат.