Ответ 1
Использование макросов типа DEFER
и сложной макрологии C в целом зависит от понимания того, как препроцессор C фактически расширяет макрокоманды. Он не просто пытается уменьшить все деревья выражений так, как это делает обычный язык программирования, а скорее работает на потоке линейных токенов и имеет неявный "курсор" в том месте, где он в настоящее время исследует поток для возможных замещений. В любом заданном "стеке" процесса расширения курсор никогда не перемещается назад, и как только токен передан в потоке, он не проверяется снова.
Просматривая первый пример операции DEFER
:
DEFER(A)() // cursor starts at the head of the sequence
^ // identifies call to DEFER - push current position
DEFER( A )() // attempt to expand the argument (nothing to do)
^
// replace occurrences of id in DEFER with A,
// then replace the call to it with the substituted body
A EMPTY() () // pop cursor position (start of pasted subsequence)
^ // doesn't find an expansion for A, move on
A EMPTY() () // move to next token
^ // EMPTY() is a valid expansion
A () // replace EMPTY() with its body in the same way
^ // continuing...
A () // `(` is not a macro, move on
^
A ( ) // `)` is not a macro, move on
^
A () // end of sequence, no more expansions
^
Курсор перемещался после A
во время "повторного сканирования" тела DEFER
после замены аргументов, что является второй и последней попыткой расширить этот набор токенов. Как только курсор переместился через A
, он не возвращается к нему во время этой последовательности расширения, а так как "повторное сканирование" находится на верхнем уровне, следующая последовательность расширений отсутствует.
Теперь рассмотрим одно и то же выражение, но завернутый в вызов EXPAND
:
EXPAND(DEFER(A)()) // cursor starts at the head etc.
^ // identifies call to EXPAND
EXPAND( DEFER(A)() ) // attempt to expand the argument
^ // this does the same as the first
// example, in a NESTED CONTEXT
// replace occurrences of __VA_ARGS__ in EXPAND with A ()
// then replace the call with the substituted body
A () // pop cursor position (start of pasted subsequence)
^ // identifies A, and can expand it this time
Поскольку списки аргументов расширяются в сложном контексте, а позиция курсора восстанавливается в позиции перед исходным вызовом для повторного сканирования, помещая вызов макроса в любой список аргументов макроса - даже тот, который на самом деле ничего не делает, как EXPAND
- дает ему "свободный" дополнительный пробег курсором расширения, путем сброса позиции курсора в потоке дополнительное время для дополнительного повторного сканирования и, следовательно, предоставление каждого свежеприведенного выражения вызова (то есть, имя макроса и список аргументов в скобках) дополнительный шанс на распознавание расширителем. Таким образом, все EVAL
это дает вам 363 (3 ^ 5 + 3 ^ 4 + 3 ^ 3 + 3 ^ 2 + 3, кто-то проверяет мою математику), бесплатный повторный поиск проходит.
Итак, обращаясь к вопросам в свете этого:
- "painting blue" не работает совсем так (объяснение в вики немного ошибочно сформулировано, хотя это не так). Имя макроса, если оно сгенерировано внутри этого макроса, будет окрашено в синий цвет постоянно (C11 6.10.3.4 "[blue] токены больше не доступны для дальнейшей замены, даже если они позже (повторно) рассмотрены" ). Точка
DEFER
скорее состоит в том, чтобы гарантировать, что рекурсивный вызов не будет сгенерирован на проходе расширения макроса, но вместо этого... отложен... до внешнего этапа сканирования, после чего он не будет окрашен синий, потому что мы больше не внутри этого имени. Вот почемуREPEAT_INDIRECT
функционально; так что это может быть предотвращено от расширения во что-либо, упоминающее имяREPEAT
, если мы все еще находимся в телеREPEAT
. Он требует, по меньшей мере, одного дополнительного свободного прохода после того, как исходныйREPEAT
завершает, чтобы развернуть маркерыEMPTY
. - да,
EXPAND
заставляет дополнительный проход расширения. Любой вызов макроса предоставляет один дополнительный проход расширения к любому выражению, переданному в списке аргументов.- Идея
DEFER
заключается в том, что вы не передаете ей целое выражение, а только часть "функции"; он вставляет разделитель между функцией и ее списком аргументов, который стоит один проход расширения для удаления. - поэтому разница между
EXPAND
иDEFER
заключается в том, чтоDEFER
налагает потребность в дополнительном проходе до того, как функция, которую вы передаете, расширится; иEXPAND
предоставляет дополнительный проход. Таким образом, они друг друга обратны (и применяются вместе, как и в первом примере, эквивалентны вызову, который не используется).
- Идея
- да,
OBSTRUCT
стоит два прохода расширения до того, как функция, которую вы передаете, будет расширена. Это делаетDEFER
расширение спейсераEMPTY()
одним (EMPTY EMPTY() ()
), сжигая первый курсор reset при избавлении от вложенногоEMPTY
. -
OBSTRUCT
требуется вокругREPEAT_INDIRECT
, потому чтоWHEN
расширяет (по истине) на вызовEXPAND
, который сгорит один слой косвенности, пока мы все еще находимся в пределах вызоваREPEAT
. Если бы существовал только один слой (aDEFER
), вложенныйREPEAT
был бы сгенерирован, пока мы все еще находимся вREPEAT
контексте, в результате чего он будет окрашен в синий цвет и будет убивать рекурсию прямо там. Использование двух уровней сOBSTRUCT
означает, что вложенныйREPEAT
не будет создан до тех пор, пока мы не достигнем повторного сканирования любого макрокоманда внеREPEAT
, после чего мы сможем безопасно сгенерировать имя снова, потому что он выскочил из стека без расширения.
Таким образом, этот метод рекурсии работает с использованием внешнего источника большого количества повторов сканирования (EVAL
) и откладывает расширение имени макроса внутри себя по меньшей мере одним проходом повторного сканирования, поэтому он происходит дальше стек вызовов. Первое расширение тела REPEAT
происходит во время повторного сканирования EVAL[363]
, рекурсивный вызов откладывается до повторного сканирования EVAL[362]
, отложенного вложенного расширения... и так далее. Это не истинная рекурсия, поскольку она не может образовать бесконечный цикл, но вместо этого полагается на внешний источник кадров стека для записи.