#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()
вызова, и в тексте замены этого макроса соответствующий параметр не используется в качестве операнда оператора#
или##
. Вы бы достигли того же результата, начав с этого в первую очередь.