Макрос Тело замены
Рис. 16,2. Части определения функционального макроса
668 Глава 16
Вот пример определения:
#define SQUARE(X) Х*Х
Оно может применяться в программе следующим образом:
z = SQUARE(2);
Оператор выглядит похожим на вызов функции, хотя поведение макроса не обязательно будет идентичным. В листинге 16.2 иллюстрируется использование этого и второго макроса. В некоторых примерах также обращается внимание на возможные ловушки, поэтому читайте их внимательно.
Листинг 16.2. Программа mac arg.c

Макрос SQUARE имеет следующее определение:
#define SQUARE(X) Х*Х
Здесь SQUARE — идентификатор макроса, X в SQUARE (X) — аргумент макроса, а Х*Х — список замены. Каждое вхождение SQUARE (х) в листинге 16.2 заменяется х*х. Отличие данного примера от предыдущих состоит в возможности использования в макросе любых символов помимо X. Символ X в определении макроса заменяется символом, который указан при вызове макроса в программе. Таким образом, SQUARE (2) заменяется 2*2, так что X действительно играет роль аргумента.
Однако, как вскоре будет показано, аргумент макроса не работает в точности как аргумент функции. Ниже представлены результаты выполнения программы. Обратите внимание, что некоторые вычисления дают результат, отличающийся от того, что можно было ожидать. На самом деле ваш компилятор может даже выдать не такой результат, как приведенный в предпоследней строке:
Препроцессор и библиотека С 669
х = 5
Вычисление SQUARE(x): Результат: 25.
Вычисление SQUARE(2): Результат: 4.
Вычисление SQUARE(x+2): Результат: 17.
Вычисление 100/SQUARE(2): Результат: 100.
х = 5.
Вычисление SQUARE(++x): Результат: 42.
После инкрементирования х = 7.
Первые две строки вполне предсказуемы, но затем встречается несколько странных результатов. Вспомните, что х имеет значение 5. Это может привести к предположению, что SQUARE (х+2) должно быть 7*7, или 49, но выводится 17 — простое число, но определенно не квадрат! Причина такого вводящего в заблуждение вывода связана с тем, что, как уже говорилось, препроцессор не выполняет вычислений, а просто заменяет последовательности символов. Где бы в определении не появлялось х, препроцессор подставит вместо х символы х+2. Поэтому
х*х
принимает вид:
х+2*х+2
Единственным умножением является 2*х. Если х равно 4, выражение вычисляется следующим образом:
4 + 2*4 + 2 = 4 + 8 + 2 = 14
Этот пример подчеркивает важное отличие между вызовом функции и вызовом макроса. При вызове функции ей передается значение аргумента во время выполнения программы. При вызове макроса лексема аргумента передается в программу перед компиляцией; это другой процесс, происходящий в другое время. Можно ли исправить определение, чтобы вызов SQUARE (х+2) выдавал 36? Конечно. Нужны просто дополнительные круглые скобки:
#define SQUARE(х) (х)*(х)
Теперь SQUARE (х+2) превращается в (х+2) * (х + 2) , и вы получите ожидаемое умножение, поскольку круглые скобки останутся в заменяющей строке.
Однако это не решает всех проблем. Рассмотрим события, которые приводят к тому, что следующая строка:
100/SQUARE(2)
в выводе преобразуется к виду
100/2*2
Согласно приоритетам операций, выражение вычисляется слева направо: (100/2) *2, или 50*2, или 10 0. Для устранения путаницы SQUARE (х) необходимо определить так:
#define SQUARE(х) (х*х)
В результате это дает 100/ (2*2), что в итоге вычисляется как 100/4, или 25.
В следующем определении учтены ошибки обоих примеров:
#define SQUARE(х) ((х)*(х))
Из всего продемонстрированного можно извлечь такой урок: применяйте столько круглых скобок, сколько необходимо для того, чтобы обеспечить корректный порядок выполнения операций.
670 глава 16
Но даже эти меры предосторожности не спасают от ошибки в последнем примере:
SQUARE(++х)
В результате получается:
+ + Х* + +х
Здесь х инкрементируется дважды — один раз до операции умножения и один раз после нее:
++х*++х = 6*7 = 42
Из-за того, что выбор конкретного порядка выполнения операций оставлен за разработчиками реализаций, некоторые компиляторы генерируют умножение 7*6. Есть компиляторы, которые могут инкрементировать оба операнда перед умножением, выдавая в результате 7*7, или 49. На самом деле вычисление этого выражения приводит к ситуации, которая в стандарте называется неопределенным поведением. Тем не менее, во всех этих случаях х начинает со значения 5 и заканчивает значением 7, хотя код выглядит так, будто инкрементирование происходит только один раз.
Читать дальше