Макрос — это короткая процедура, у которой могут быть аргументы.
Макрос определяется с помощью директивы препроцессора #define.
Определение макроса похоже на определение поименованной константы.
#define идентификатор(аргумент[,аргумент]...) строка-шаблон
В скобках непосредственно после идентификатора могут быть указаны имена аргументов. Между идентификатором и символом ( не должно быть символов промежутка. При расширении макроса препроцессор осуществляет вставку строк. Препроцессор ничего не знает о синтаксисе языка Си или логике программы. Поэтому такие подстановки могут быть источниками синтаксических ошибок.
Пример 1
#define SQUARE(X) ((X)*(X))
#define MAX(A,B) ((A)>(B)?(A):(B))
int main()
{
  int int1, int2;
  int1=SQUARE(3);
  int2=SQUARE(int1+1);
  printf("max = %d\n", MAX(int1,int2));
}
Результат препроцессирования:
int main()
{
  int int1, int2;
  int1 = ((3)*(3));
  int2 = ((int1+1)*(int1+1));
  printf ("max = %d\n", ((int1)>(int2)?(int1):(int2)));
}
Вывод программы:
max = 100
Если в прим. 1 в определении SQUARE не будут использованы скобки, то переменной int2 будет присвоено другое значение:
int2 = int1 + 1 * int1 + 1;
Для просмотра результата препроцессирования надо использовать команду компиляции cc с опцией -E.
Макрос может использовать другие ранее определенные макросы. Многие препроцессоры Си, включая препроцессор системы ОС UNIX, не допускают использования рекурсивных макросов.
Иногда удобно определять макрос так, чтобы он был блоком операторов.
Одно из преимуществ блока — возможность определить локальные переменные. Если только макрос содержит более одного оператора, эти операторы должны быть включены в блок, иначе возможны неприятности.
Пример 2
/* Эти операторы следует заключить в блок */
#define DOIT(A, B, C) A=D*2; \
                      B=C*2;
void f(void)
{
   int i, j, k;
   if (i<j)
       DOIT(i, j, k) /* Здесь после обработки препроцессором будет синтаксическая ошибка */
   else
      printf("Something \n");
}
Хотя этот фрагмент программы и выглядит корректно, однако после расширения препроцессором компилятор обнаружит ошибку. Синтаксис оператора if-else требует единственного оператора между if и else. В такой ситуации использование блока в макросе устранит проблему.
Стандарт ANSI языка Си запрещает макроподстановки в строковых литералах.
Операция #
Операция #- операция для создания строки из аргумента макроса.
Пример 3
Определение макроса:
#define string(x) #x
Использование макроса:
string(hello)
Результат подстановки:
"hello"
Пример 4
#define SWAP(A,B) {int temp; \
                    temp = A;\
                    A = B;\
                    B =temp; \
                    }
#define PRINT(A,B) \
            printf(#A": %d, "#B": %d\n", A, B)
int main( )
{
  int num1 = 30, num2 = 90;
  PRINT(num1, num2);
  if (num2 > num1)
     SWAP(num1, num2);
  PRINT(num1, num2);
}
Вывод программы:
num1: 30, num2: 90
num1: 30, num2: 90
Операция ##
Операция ## — операция для объединения лексем в определении макросов в одну лексему .
Пример 5
#define WheatBread 0
#define RyeBread 1
#define WhiteBread 2
#define PumpernickelBread 3
#define Bread(x) x ## Bread
int main ()
{
    printf("Value of WheatBread is %d\n", Bread(Wheat));
    printf("Value of RyeBread is %d\n", Bread(Rye));
    printf("Value of WhiteBread is %d\n", Bread(White));
}
Вывод:
Value of WheatBread is 0
Value of RyeBread is 1
Value of WhiteBread is 2
Вызов макроса Bread(Rye) расширяется сначала в Rye##Bread, потом – в одно слово RyeBread, заменяемое затем константой 1.
Сравнение функций и макросов
Большим преимуществом макросов является то, что они быстрее функций. Потери времени при вызове функции включают в себя время на сохранение аргументов и регистров на стеке и восстановление их при возврате. При вызове макроса этого не происходит.
Однако, если макрос используется много раз, исполняемая программа будет иметь больший размер, чем при использовании функций.
Преимуществом функций является возможность возвратить результат при помощи оператора return. В макросе оператор return вызовет немедленный выход из функции, в которой вызван макрос.
Кроме того, функции могут быть рекурсивными. Большая же часть препроцессоров Си (в том числе и в операционной системе UNIX) запрещают рекурсивные макросы.
К тому же макросы сложнее для отладки, чем функции. Отлаживая макрос, надо смотреть на результат работы препроцессора. Для этого в операционной системе UNIX можно воспользоваться следующими командами:
$cc -E -o proc.i proc.c
$cat proc.i