Как именно работает трюк с двойной строкой?

84

По крайней мере, некоторые препроцессоры C позволяют вам преобразовать значение макроса в строку, а не его имя, передав его через один функционально-подобный макрос другому, который преобразовывает его в строку:

Примеры использования здесь .

Это работает, по крайней мере, в GCC и Clang (оба с -std=c99), но я не уверен, как это работает в терминах стандарта C.

Гарантирует ли это поведение C99?
Если да, то как C99 это гарантирует?
Если нет, то в какой момент поведение переходит от C-определенного к GCC?

Питер Хози
источник
1
Если вы говорите «хоть какой-то», значит ли это, что вы видели один, где он не работает? Я готов написать поставщику отчет об ошибке.
Йенс
@Jens: Нет; Я нет. Каждый компилятор, который я использовал (а именно, GCC и Clang), реализует это поведение.
Питер Хози

Ответы:

80

Да, это гарантировано.

Это работает, потому что аргументы макроса сами раскрываются макросом, за исключением случаев, когда имя аргумента макроса появляется в теле макроса со строковым указателем # или лексем-вставкой ##.

6.10.3.1/1:

... После того, как аргументы для вызова функционально-подобного макроса были идентифицированы, происходит подстановка аргументов. Параметр в списке замены, если ему не предшествует токен предварительной обработки # или ## или за ним не следует токен предварительной обработки ## (см. Ниже), заменяется соответствующим аргументом после того, как все содержащиеся в нем макросы были раскрыты ...

Итак, если вы это сделаете, STR1(THE_ANSWER)вы получите «THE_ANSWER», потому что аргумент STR1 не раскрывается макросом. Тем не менее, аргумент Str2 является макро-расширена , когда она замещена в определение Str2, который , следовательно , дает Str1 аргумент 42, с результатом «42».

Стив Джессоп
источник
21

Как отмечает Стив, это гарантировано, и это было гарантировано со времен стандарта C89 - это был стандарт, кодифицировавший операторы # и ## в макросах и предписывающий рекурсивно расширять макросы в args перед их заменой в тело, если и только если тело не применяет # или ## к аргументу. В этом отношении C99 не отличается от C89.

Крис Додд
источник
+1 за то, что нашел время, чтобы подтвердить, что он также является частью C89. Некоторые из нас заботятся о переносимости, в том числе о создании кода, который люди, компилирующие в режиме C89, могут использовать - всего несколько лет назад для выпусков gcc по-прежнему использовался C89 (плюс расширения gcc, если честно), MSVC, о котором я слышал, по-прежнему поддерживает только C89 и встроенные или устаревшие системы также иногда имеют только компиляторы C89. C89 обеспечивает отличную переносимость «первого этажа», стандарт с наименьшим общим знаменателем, который люди могут нацелить на компиляцию и запуск для чего-либо, находящегося в практическом использовании сегодня. Так что очень приятно видеть, что это вспоминают.
mtraceur