Почему этот макрос заменен на 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 результат последнего расширения) Дальнейшее расширение не возможно.