Почему этот макрос заменен на 20 вместо 10?
1. #define NUM 10
2. #define FOO NUM
3. #undef NUM
4. #define NUM 20
5.
6. FOO
Когда я запускаю только препроцессор, выходной файл содержит 20.
Однако из того, что я понимаю, препроцессор просто выполняет замену текста. Так что это то, что я думаю, происходит (что, очевидно, неправильно, но idky):
- NUM определяется как 10.
- Поэтому в строке 2 NUM заменяется на 10. Итак, теперь мы имеем "#define FOO 10".
- NUM - undefined.
- NUM переопределяется и теперь составляет 20.
- FOO заменяется в соответствии со строкой 2, которая была до переопределения строки 4 и равна 10.
Итак, я думаю, что результат должен быть 10, а не 20. Может ли что-нибудь объяснить, где оно пошло не так?
Ответы
Ответ 1
В интересах сбора всех соответствующих спецификаций из стандартов я извлек эту информацию из потока комментариев и добавил номера разделов С++ на основе проекта N4527 (нормативный текст идентичен в двух стандартах). Стандарт абсолютно ясен по этому вопросу.
-
#define
директивы препроцессора не подвергаются макросъемке.
(C11 & sect; 6.10 & para; 7; С++ & sect; 16 [cpp] & para; 6): токены предварительной обработки в директиве предварительной обработки не подпадают под макрорасширение, если не указано иное.
-
После замены макроса заменяемым текстом новый текст будет повторно сканирован. Метки препроцессора в замене расширяются как макросы, если в этой точке программы есть активное определение макроса для токена.
(C11 & sect; 6.10.3 & para; 9; С++ & sect; 16.3 [cpp.replace] & para; 9) Директива предварительной обработки формы
# define identifier replacement-list new-line
определяет объект-подобный макрос, который заменяет каждый последующий экземпляр имени макроса заменяющим списком токенов предварительной обработки, которые составляют оставшуюся часть директивы. Затем список замещения повторно сканируется для большего количества имен макросов, как указано ниже.
-
Определение макроса активно из строки, следующей за #define
, до #undef
для имени макроса или конца файла.
(C11 & sect; 6.10.3.5 & para; 1; С++ & sect; 16.3.5 [cpp.scope] & para; 1) Определение макроса длится (независимо от структуры блока) до тех пор, пока не будет встречена соответствующая директива #undef
или ( если они не встречаются) до конца блока перевода предварительной обработки. Определения макросов не имеют значения после фазы перевода 4.
Если мы посмотрим на программу:
#define NUM 10
#define FOO NUM
#undef NUM
#define NUM 20
FOO
мы видим, что определение макроса NUM
в строке 1 длится ровно до строки 3. В этих строках нет сменного текста, поэтому определение никогда не используется; следовательно, программа фактически будет такой же, как:
#define FOO NUM
#define NUM 20
FOO
В этой программе на третьей строке есть активное определение для FOO
, с заменяющим списком NUM
и для NUM
, с заменяющим списком 20
. FOO
заменяется списком замены, делая его NUM
, а затем снова проверяется на наличие макросов, в результате заменяя NUM
на свой список замены 20. Эта замена снова повторно сканируется, но нет так что конечным результатом является то, что токен 20
оставлен для обработки в фазе трансляции 5.
Ответ 2
Замена текста выполняется там, где используется макрос, а не там, где вы написали #define
. В момент, когда вы используете FOO
, он заменяет FOO
на NUM
и NUM
в настоящее время определяется как 20
.
Ответ 3
В:
FOO
препроцессор заменит его на NUM
, затем он заменит NUM
тем, что в настоящее время определено как 20
.
Эти начальные четыре строки эквивалентны:
#define FOO NUM
#define NUM 20
Ответ 4
В стандарте C11 (и другие версии C и С++ говорят так же):
Директива предварительной обработки формы # define identifier replacement-list new-line
определяет объект-подобный макрос, который заменяет каждый последующий экземпляр имени макроса заменяющим списком токенов предварительной обработки, которые составляют оставшуюся часть директивы. Затем список замещения повторно сканируется для большего количества имен макросов, как указано ниже.
Однако он также говорит в другой части (спасибо rici за указание этого).
Знаки предварительной обработки в директиве предварительной обработки не подпадают под макрораспределение, если не указано иное.
Итак, следующий экземпляр имени макроса, который находится внутри другой директивы #define
, фактически заменен не.
Ваша строка #define FOO NUM
определяет, что, когда токен FOO
будет найден позже (за пределами другой директивы #define
!), он будет заменен токеном NUM
.
После замены токена происходит повторное сканирование, а если NUM
сам является макросом, то в этой точке заменяется NUM
. (И если любой NUM
расширяется до содержит макросы, то это расширяется и т.д.).
Итак, ваша последовательность шагов:
-
NUM
определяется как 10
-
FOO
определяется как NUM
-
NUM
undefined и переопределяется как 20
-
FOO
расширяется до NUM
- (повторное сканирование)
NUM
расширяется до 20
Это поведение можно увидеть в другом общем трюке препроцессора, чтобы превратить определенное значение макроса в строку:
#define STR(X) #X
#define STR_MACRO(X) STR(X)
#define NUM 10
puts( STR_MACRO(NUM) ); // output: 10
Если бы мы написали puts( STR(NUM) )
, тогда выход был бы NUM
.
Вывод 10
возможен, потому что, как и раньше, второй #define
здесь фактически не расширяется STR
. Таким образом, последовательность шагов в этом коде:
-
STR(X)
определяется как #X
-
STR_MACRO(X)
определяется как STR(X)
-
NUM
определяется как 10
-
STR_MACRO
и NUM
расширяются; результат puts( STR(10) );
- (Rescan результат последнего расширения)
STR(10)
расширяется до "10"
- (Rescan результат последнего расширения) Дальнейшее расширение не возможно.