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

Для начала приведем распространенные ошибки:
1.
# define inc (x) ((x) + 1)
Ошибка: между именем макроса "inc" и его аргументом "(x)" стоит пробел, поэтому при использовании этого макроса, например, "inc(10)", слово "inc" будет заменено на "(x) ((x) + 1)" и работать, естесственно, ничего не будет.
2.
#define N 12;
В этом случае после числа "12" стоит точка с запятой (возможно, опечатка), поэтому при использовании такого макроса, хотя бы так: "x = N-1;" вылезет синтаксическая ошибка, поскольку строка преобразуется к виду: "x = 12;-1;"
3.
#define X -2
По сути, тут ошибки нет, однако, если мы будем использовать этот макрос в строках вида:
p=4-X;
компилятор выдаст ошибку, поскольку эта строка будет преобразована к виду:
p=4--2;
которая, конечно же, неверна.
Для избежания подобных ошибок лучше писать через пробелы:
p = 4 - X;
но лучше все-таки правильно описывать макросы: брать определения в скобки:
#define X (-2)


Теперь рассмотрим несколько примеров, показывающих, как правильно надо писать макросы:

1. Макрос, определяющий модуль числа:
#define abs(x) ((x) < 0 ? -(x) : (x))
Зачем аргумент "х" брать в круглые скобки?
Предположим, что мы написали без скобок:
#define abs(x) (x < 0 ? -x : x)
Тогда при использовании макроса, например, в виде "abs(-z)" в результате макроподстановки мы получим: "(-z < 0 ? --z : -z)", что, конечно, неверно.
Зачем использовать внешние скобки?
Пусть объявлен макрос без внешних скобок:
#define div(a,b) (a)/(b)
Тогда при его использовании в такой строке:
z = sizeof div(1,2);
в результате подстановки получим:
z = sizeof (1)/2;
Это совсем не то, что мы ожидали: "sizeof(int)/2" вместо "sizeof(int)".
При использовании внешних скобок:
#define div(a,b) (a)/(b)
все будет ОК.

Как известно, в теле макроопределения могут находиться операторы, заключенные в фигурные скобки {...}. При использовании таких макросов в условных операторах if-else могут возникать ошибки.
Пусть объявлен макрос:

#define MACRO { x=1; y=2; }

и мы используем его в виде:

if(z) MACRO;
else .......;

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

if(z) { x=1; y=2; } /* конец if-а */ ;
else .......; /* else ни к чему не относится */

то есть синтаксически ошибочный фрагмент, так как должно быть либо

if(...) один_оператор;
else .....
либо
if(...){ последовательность; ...; операторов; }
else .....

где точка-с-запятой после } не нужна. С этим явлением борются, оформляя блок {...} в
виде do{...}while(0)

#define MACRO do{ x=1; y=2; }while(0)

Тело такого "цикла" выполняется единственный раз, при этом мы получаем правильный текст:

if(z) do{ x=1; y=2; }while(0);
else .......;


Макросы, в отличие от функций, могут порождать непредвиденные побочные эффекты:

int sqr(int x){ return x * x; }
#define SQR(x) ((x) * (x))
main(){ int y=2, z;
z = sqr(y++); printf("y=%d z=%d\n", y, z);
y = 2;
z = SQR(y++); printf("y=%d z=%d\n", y, z);
}

Вызов функции sqr печатает "y=3 z=4", как мы и ожидали. Макрос же SQR расширяется в

z = ((y++) * (y++));

и результатом будет "y=4 z=6", где z совсем не похоже на квадрат числа 2.

Как видно, при применении макросов, существует много "подводных камней", поэтому перед их написанием подумайте как следует, и точто представляйте, как они будут использованы и на что будут заменены. Далеко не всегда при использовании неправильных макросов компилятор выдаст ошибку. Программа будет глючить, но вы не будете понимать, почему. В этом случае иногда бывает полезно посмотреть исходники после обработки препроцессором (во многих компиляторах такие опции есть).
Очень надеюсь, что написанное здесь кому-то пригодится и поможет избежать лишних ошибок.
Данная статья основана на примерах из книги Андрея Богатырева "Хрестоматия по программированию на Си в Unix". Очень хорошая книжка, много примеров. Если есть время, советую почитать.

 










 


Последнее изменение: Понедельник, 5 сентября 2011, 12:30