Директива #define

Содержание

Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены — макрозаменой[1]. В общем виде директива выглядит таким образом:

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

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

Предположим, например, что вместо значения 1 нужно использовать слово LEFT (левый), а вместо значения 0 — слово RIGHT (правый). Тогда можно сделать следующие объявления с помощью директивы #define:

#define LEFT 1
#define RIGHT 0

В результате компилятор будет подставлять 1 или 0 каждый раз, когда в вашем файле исходного кода встречается идентификатор соответственно LEFT или RIGHT. Например, следующий код выводит на экран 0 1 2:

printf("%d %d %d", RIGHT, LEFT, LEFT+1);

После определения имя макроса можно использовать в определениях других имен макросов. Вот, например, код, определяющий значения ONE (один), TWO (два) и three (три):

#define ONE    1 
#define TWO    ONE+ONE
#define THREE  ONE+TWO

Макроподстановка — это просто замена какого-либо идентификатора связанной с ним последовательностью символов. Поэтому если требуется определить стандартное сообщение об ошибке, то можно написать примерно следующее:

#define E_MS "стандартная ошибка при вводе\n"
/* ... */
printf(E_MS);

Теперь каждый раз, когда встретится идентификатор E_MS, компилятор будет его заменять строкой «стандартная ошибка при вводе\n». Для компилятора выражение printf() на самом деле будет выглядеть таким образом:

printf("стандартная ошибка при вводе\n");

Если идентификатор находится внутри строки, заключенной в кавычки, то замены не будет. Например, при выполнении кода

#define XYZ это проверка

printf("XYZ");

вместо сообщения это проверка будет выводиться последовательность символов XYZ.

Если последовательность_символов не помещается в одной строке, то эту последовательность можно продолжить на следующей строке, поместив в конце предыдущей, как показано ниже, обратную косую черту:

#define LONG_STRING "это очень длинная \
строка, используемая в качестве примера"

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

Имена макросов часто используются для определения имен так называемых «магических чисел» (встречающихся в программе). Например, имеется программа, в которой определяется массив и несколько процедур, получающих доступ к этому массиву. Вместо того чтобы размер массива «зашивать в код» в виде константы, этот размер можно определить с помощью оператора #define, а затем использовать это имя макроса везде, где требуется размер массива. Таким образом, если требуется изменить этот размер, то потребуется изменить только соответствующий оператор #define, a затем перекомпилировать программу. Рассмотрим, например, фрагмент программы

#define MAX_SIZE 100
/* ... */
float balance[MAX_SIZE];
/* ... */
for(i=0; i<MAX_SIZE; i++) printf("%f", balance[i]);
/* ... */
for(i=0; i<MAX_SIZE; i++) x =+ balance[i];

Размер массива balance определяется именем макроса MAX_SIZE, и поэтому если этот размер потребуется в будущем изменить, то надо будет изменить только определение MAX_SIZE. В результате при перекомпиляции программы все обращения к этому имени макроса, находящиеся после измененного определения, будут автоматически изменены.

Определение макросов с формальными параметрами


У директивы #define имеется еще одно большое достоинство: имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами[2]. Например,

#include <stdio.h>

#define ABS(a)  (a) < 0 ? -(a) : (a)

int main(void)
{
  printf("модули чисел -1 и 1 равны соответственно %d и %d",
         ABS(-1), ABS(1));

  return 0;
}

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

ABS(10-20)

после макрозамены будет преобразовано в

10-20 < 0 ? -10-20 : 10-20

и может привести к неправильному результату.

Использование вместо настоящих функций макросов с формальными параметрами дает одно существенное преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы.

И вот еще что: хотя макросы с формальными параметрами являются полезным средством, но в С99 (и в C++) есть еще более эффективный способ создания машинной программы — с использованием ключевого слово inline.



На заметкуВ С99 можно определить макрос с переменным количеством формальных параметров; об этом рассказывается в части II этой книги.


----------
[1]А также макрорасширением, макрогенерацией и макроподстановкой. Определение макроса часто называют макроопределением, а обращение к макросу — макровызовом или макрокомандой. Впрочем, иногда макроопределение также называется макрокомандой.
[2]А также макроопределениями с параметрами и макросами, напоминающими функции.