4.8.4. Удвоение побочных эффектов
Во многих С программах определяется макрос 'min' для вычисления
минимума:
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
При вызове этого макроса вместе с аргументом, содержащим побочный
эффект, следующим образом:
next = min (x + y, foo (z));
он заменяется на строку
next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));
где значение 'x + y' подставляется вместо 'X', а 'foo (z)' - вместо 'Y'.
Функция 'foo' используется в этой конструкции только один раз, в то
время как выражение 'foo (z)' используется дважды в макроподстановке. В
результате функция 'foo' может быть вызвана дважды при выполнении выражения.
Если в макросе имеются побочные эффекты или для вычисления значений
аргументов требуется много времени, результат может быть неожиданным. В
данном случае макрос 'min' является ненадежным.
Наилучшим решением этой проблемы является определение макроса 'min'
таким образом, что значение 'foo (z)' будет вычисляться только один раз. В
языке С нет стандартных средств для выполнения подобных задач, но с
использованием расширений GNU C это может быть выполнено следующим образом:
#define min(X, Y) \
({ typeof (X) __x = (X), __y = (Y); \
(__x < __y) ? __x : __y; })
Если не использовать расширения GNU C, то единственным решением будет
осторожное применение макроса 'min'. Например, для вычисления значения
'foo (z)', можно сохранить его в переменной, а затем использовать ее значение
при вызова макроса:
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
...
{
int tem = foo (z);
next = min (x + y, tem);
}
(здесь предполагается, что функция 'foo' возвращает значение типа 'int').
4.8.5. Рекурсивные макросы
"Рекурсивные" макросы - это макросы, в определении которых используется
имя самого макроса. Стандарт ANSI C не рассатривает рекурсивный вызов
макроса как вызов. Он поступает на вывод препроцессора без изменений.
Рассмотрим пример:
#define foo (4 + foo)
где 'foo' также является переменной в программе.
Следуя обычным правилам, каждая ссылка на 'foo' заменяется на значение
'(4 + foo)', затем это значение просматривается еще раз и заменяется на
'(4 + (4 + foo))' и так далее, пока это не приведет к ошибке (memory full)
препроцессора.
Однако, правило об использовании рекурсивных макросов завершит этот
процесс после получения результата '(4 + foo)'. Поэтому этот макрос может
использоваться для прибавления 4 к значению переменной 'foo'.
В большинстве случаев не следует опираться на эту возможность. При
чтении исходных текстов может возникнуть путаница между тем, какое значение
является переменной, а какое - вызовом макроса.
Также используется специальное правило для "косвенной" рекурсии. Здесь
имеется в виду случай, когда макрос X заменяется на значение 'y', которое
является макросом и заменяется на значение 'x'. В результате ссылка на
макрос 'x' является косвенной и происходит от подстановки макроса 'x', таким
образом, это является рекурсией и далее не обрабатывается. Поэтому после
обработки
#define x (4 + y)
#define y (2 * x)
'x' заменяется на '(4 + (2 * x))'.
Но предположим, что 'y' используется где-либо еще и не в определении
макроса 'x'. Поэтому использование значения 'x' в подстановке макроса 'y'
не является рекурсией. Таким образом, производится подстановка. Однако,
подстановка 'x' содержит ссылку на 'y', а это является косвенной рекурсией.
В результате 'y' заменяется на '(2 * (4 + y))'.
Неизвестно где такие возможности могут быть использованы, но это
определено стандартом ANSI C.
4.8.6. Отдельная подстановка макро аргументов
Ранее было объяснено, что макроподстановка, включая подставленные
значения аргументов, заново просматривается на предмет наличия новых макро
вызовов.
Что же происходит на самом деле, является довольно тонким моментом.
Сначала значения каждого аргумента проверяются на наличие макро вызовов.
Затем полученные значения подставляются в тело макроса и полученная макро
подстановка проверяется еще раз на наличие новых макросов.
В результате значения макроаргументов проверяются дважды.
В большинстве случаев это не дает никакого эффекта. Если аргумент
содержит какие-либо макро вызовы, то они обрабатываются при первом проходе.
Полученное значение не содержит макро вызовов и при втором проходе оно не
изменяется. Если же аргументы будут подставлены так, как они были указаны,
то при втором проходе, в случае наличия макро вызовов, будет произведена
макроподстановка.
Рекурсивный макрос один раз подставляется при первом проходе, а второй
раз - при втором. Не подставляемые рекурсивные элементы при выполнении
первого прохода отдельно помечаются и поэтому они не обрабатываются при
втором.
Первый проход не выполняется, если аргумент образован путем
стрингификации или объединения. Поэтому
#define str(s) #s
#define foo 4
str (foo)
заменяется на '"foo"'.
При стрингификации и объединении аргумент используется в таком виде, в
каком он был указан без последующего просмотра его значения. Этот же аргумент
может быть просмотрен, если он указан где-либо еще без использования
стрингификации или объединения.
#define str(s) #s lose(s)
#define foo 4
str (foo)
заменяется на '"foo" lose(4)'.
Возникает вопрос: для чего используется два прохода для просмотра
макроса и почему бы не использовать один для повышения скорости работы
препроцессора. В действительности, здесь есть некоторая разница и она
может быть видна в трех отдельных случаях:
При однородных вызовах макросов.
При использовании макросов, вызывающих другие макросы, которые
используют стрингификацию или объединение.
При использовании макросов, содержащих открытые запятые.
Макро вызовы называются "однородными", если аргумент этого макроса
содержит вызов этого же макроса. Например, 'f' это макрос, принимающий
один аргумент, а 'f (f (1))' является однородной парой вызовов макроса 'f'.
Требуемая подстановка производится путем подстановки значения 'f (1)' и
его замены на определение 'f'. Дополнительный проход приводит к желаемому
результату. Без его выполнения значение 'f (1)' будет заменено как
аргумент и во втором проходе оно не будет заменено, так как будет является
рекурсивным элементом. Таким образом, применение второго прохода
предотвращает нежелательный побочный эффект правила о рекурсивных макросах.
Но применение второго прохода приводит к некоторым осложнениям в
отдельных случаях при вызовах однородных макросов. Рассмотрим пример:
#define foo a,b
#define bar(x) lose(x)
#define lose(x) (1 + (x))
bar(foo)
Требуется преобразовать значение 'bar(foo)' в '(1 + (foo))', которое
затем должно быть преобразовано в '(1 + (a,b))'. Но вместо этого,
'bar (foo)' заменяется на 'lose(a,b)' что в результате приводит к ошибке,
так как 'lose' принимает только один аргумент. В данном случае эта проблема
решается путем использования скобок для предотвращения неоднородности
арифметических операций:
#define foo (a,b)
#define bar(x) lose((x))
Проблема становится сложнее, если аргументы макроса не являются
выражениями, например, когда они являются конструкциями. Тогда использование
скобок неприменимо, так как это может привести к неправильному С коду:
#define foo { int a, b; ... }
В GNU C запятые можно закрыть с помощью '({...})', что преобразует
составную конструкцию в выражение:
#define foo ({ int a, b; ... })
Или можно переписать макроопределение без использования таких запятых:
#define foo { int a; int b; ... }
Существует также еще один случай, когда применяется второй проход. Его
можно использовать для подстановки аргумента с его последующей
стрингификацией при использовании двухуровневых макросов. Добавим макрос
'xstr' к рассмотренному выше примеру:
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
xstr (foo)
Здесь значение 'xstr' заменяется на '"4"', а не на '"foo"'. Причиной
этому служит то, что аргумент макроса 'xstr' заменяется при первом проходе
(так как он не использует стрингификацию или объединение аргумента). В
результате первого прохода формируется аргумент макроса 'str'. Он использует
свой аргумент без предварительного просмотра, так как здесь используется
стрингификация.
... 1-12. Напишите программу, печатающую гистограмму длин слов из файла ввода. Самое легкое - начертить гистограмму горизон- тально; вертикальная ориентация требует больших усилий. 1.7. Функции. В языке “C” функции эквивалентны подпрограммам или функ- циям в фортране или процедурам в PL/1, паскале и т.д. Функ- ции дают удобный способ заключения некоторой части вычисле- ний в черный ...
... , сложны для понимания и абсолютно непрозрачны, а возможности существенно уступают Форту и Лиспу. В общем, муть и мрак. Вавилонское столпотворение Всякий раз, когда появляется очередной новый язык, о котором говорят, как об «окончательном и безальтернативном», предрекая скорую смерть всех остальных, мне становится смешно. Сам по себе язык в отрыве от среды программирования —малоинтересен, да и все ...
... программе. В данном разделе они перечислены в алфавитном порядке и приводятся с объяснениями. Эти ошибки могут являться следствием случайного затирание памяти программой. Abnormal program termination Аварийное завершение программы Данное сообщение может появляться, если для выполнения программы не может быть выделено достаточного количества памяти. Более подробно оно рассматривается в конце ...
... доступа с записью равной байту. Такие файлы называются двоичными. Файлы прямого доступа незаменимы при написании программ, которые должны работать с большими объемами информации, хранящимися на внешних устройствах. В основе обработке СУБД лежат файлы прямого доступа. Кратко изложим основные положения работы с файлами прямого доступа. 1). Каждая запись в файле прямого доступа имеет свой номер ...
0 комментариев