Параметризованные макросы, включающие оператор ## в списке замещения

#c #c-preprocessor

#c #c-препроцессор

Вопрос:

В книге, которую я читаю «Программирование на C: современный подход«, на странице 343 есть раздел, в котором обсуждаются некоторые приемы, которые вы можете использовать, чтобы обойти определенные недостатки в макросах.

Пример проблемы изображен следующим образом:

#define CONCAT(x,y) x##y (Директива 1)

Затем автор объясняет, что следующая строка кода не будет функционировать должным образом при использовании вышеупомянутой директивы:

CONCAT(a, CONCAT(b,c)) Эта строка кода приведет к aCONCAT(b,c) в отличие от желаемого abc .

Чтобы устранить этот недостаток, автор предлагает следующий обходной путь:

#define CONCAT2(x,y) CONCAT(x,y) (Директива 2)

Автор объясняет, что наличие директивы 1 и директивы 2 гарантирует, что немного отличающаяся строка кода CONCAT2(a, CONCAT2(b,c)) будет правильно заменена на abc .

(обратите внимание, что эта строка кода отличается от исходной строки кода … CONCAT2 используется вместо CONCAT .)

Не мог бы кто-нибудь, пожалуйста, рассказать мне, почему это позволит успешно достичь желаемой цели? Насколько я понимаю, препроцессор будет продолжать сканировать предварительно скомпилированный код до тех пор, пока не будут обработаны все определенные термины. Для данного сканирования, сколько определенных слов обновляется в строке?

Я бы подумал, что происходит следующий поток предварительной обработки замен:

Дано CONCAT2(a, CONCAT2(b,c))

Первый проход по: CONCAT(a, CONCAT2(b,c))

Однако при втором переходе CONCAT расширяется до выражения списка замены? Или CONCAT2 расширяется до выражения списка замены? В любом случае, похоже, что мы снова приходим к неудачному выражению либо aCONCAT2(b,c) либо CONCAT(a, CONCAT(b,c)) , которое, следовательно, все равно завершится неудачей, как и в самом оригинальном случае, который мы представили.

Любая помощь приветствуется!

Ответ №1:

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

Таким образом, с помощью …

 CONCAT(a, CONCAT(b,c))
  

… буквенные значения обоих аргументов используются в качестве операндов для операции вставки токена. Результат таков …

 aCONCAT(b,c)
  

. Это выполняется повторное сканирование для расширения дальнейших макросов, но aCONCAT не определено как имя макроса, поэтому дальнейшее расширение макроса не происходит.

Теперь рассмотрим…

 CONCAT2(a, CONCAT2(b,c))
  

. В CONCAT2 ни один из аргументов не является операндом # или ## , поэтому перед заменой оба параметра полностью разворачиваются макросом. Конечно, a не изменяется, но CONCAT2(b,c) расширяется до CONCAT(b,c) , который при повторном сканировании расширяется до bc . Путем подстановки расширенных значений аргументов в его заменяющий текст, внешний CONCAT2 вызов расширяется до …

 CONCAT(a, bc)
  

. Затем это расширение повторно сканируется в контексте окружающего исходного текста для дальнейшего расширения макроса, что приводит к результату …

 abc
  

. Это снова повторное сканирование, но никаких дополнительных расширений макросов для выполнения нет, так что это конечный результат.

Комментарии:

1. Спасибо за ответ! Возможно, я неправильно понял, но после первого преобразования CONCAT2(a, CONCAT2(b,c)) в CONCAT2(a, CONCAT(b,c)) , почему CONCAT(b,c) расширение выполняется сначала перед CONCAT2(a, ...) расширением? Похоже, что здесь имеет место некоторый порядок приоритета?

2. Возможно, я не понимаю разницы между терминами «заменить» и «развернуть»

3. Это правильная последовательность изменений? CONCAT2(a, CONCAT2(b, c)) —> CONCAT2(a, CONCAT(b, c)) —> CONCAT2(a, bc) —> CONCAT(a, bc) —> abc

4. @S.Cramer, если вы хотите интерпретировать этапы расширения как выполнение CONCAT2(a, CONCAT(b,c)) (я бы не стал и не описывал это таким образом, но я могу с этим работать), тогда CONCAT(b, c) расширение выполняется первым, потому что CONCAT(b, c) является аргументом для (внешнего) CONCAT2() вызова, и в тексте замены этого макроса соответствующий параметр не используется в качестве операнда оператора # или ## . Вы бы достигли того же результата, начав с этого в первую очередь.