Стандартное поведение препроцессора С++
Я изучаю стандарт С++ о точном поведении препроцессора (мне нужно реализовать какой-то препроцессор С++). Из того, что я понимаю, должен быть приведен пример, который я составил (чтобы помочь моему пониманию) ниже:
#define dds(x) f(x,
#define f(a,b) a+b
dds(eoe)
su)
Я ожидаю, что первая функция, такая как вызов макроса dds(eoe)
, будет заменена на f(eoe,
(обратите внимание на запятую в заменяющей строке), которая затем рассматривается как f(eoe,su)
при повторном сканировании ввода.
Но тест с VС++ 2010 дал мне это (я сказал VС++ вывести предварительно обработанный файл):
eoe+et_leoe+et_l
su)
Это противоречит интуиции и, очевидно, неверно. Это ошибка с VС++ 2010 или мое непонимание стандарта С++? В частности, неправильно ли вводить запятую в конце строки замены, как я? Мое понимание стандартной грамматики С++ заключается в том, что там разрешен любой preprocessing-token
.
EDIT:
У меня нет GCC или других версий VС++. Может ли кто-нибудь помочь мне проверить эти компиляторы.
Ответы
Ответ 1
Насколько я понимаю, в [cpp.subst/rescan]
частях стандарта нет ничего, что делает незаконным, а clang и gcc имеют право расширять его как eoe+su
, а поведение MSC (Visual С++) должен быть указан как ошибка.
Мне не удалось заставить его работать, но мне удалось найти уродливое обходное решение MSC для вас, используя переменные - вы можете найти его полезным или не можете, но в любом случае это:
#define f(a,b) (a+b
#define dds(...) f(__VA_ARGS__)
Развертывается как:
(eoe+
su)
Конечно, это не будет работать с gcc и clang.
Ответ 2
Мой ответ действителен для препроцессора C, но согласно Является ли препроцессор С++ идентичным препроцессору C?, различия не имеют отношения к этому случаю.
Из C, Справочное руководство, 5-е издание:
Когда функция-макро-вызов активируется, весь вызов макроса заменяется после обработки параметров копией тела. параметр обработка продолжается следующим образом. Фактические строки токенов аргументов связанных с соответствующими формальными именами параметров. Копия затем создается тело, в котором каждое вхождение формального параметра имя заменяется копией фактической последовательности маркеров параметра связанные с ним. Затем эта копия тела заменяет макрос вызов. [...] Как только макровызов был расширен, сканирование макросов возобновляется в начале расширения, так что имена макросов могут быть признанным в рамках расширения с целью дальнейшего макроса замена.
Обратите внимание на слова в расширении. Это делает ваш пример недействительным. Теперь объедините его с этим: UPDATE: прочитайте комментарии ниже.
[...] Макрос вызывается путем записи его имени, левой скобки, то после фактической последовательности токенов-аргументов для каждого формального параметра, затем правую круглую скобку. Фактические последовательности токенов-аргументов разделенные запятыми.
В целом, все это сводится к тому, что препроцессор будет повторно сканировать дальнейшие макрокоманды только в рамках предыдущего расширения или если он будет продолжать читать токены, которые появляются даже после расширения.
Об этом может быть трудно думать, но я считаю, что то, что должно произойти с вашим примером, заключается в том, что имя макроса f
распознается во время повторного сканирования, а так как последующая обработка токена показывает вызов макроса для f()
, ваш пример правильно и должно выводить то, что вы ожидаете. GCC и clang дают правильный результат, и в соответствии с этим рассуждением это также будет справедливо (и выдает эквивалентные выходы):
#define dds f
#define f(a,b) a+b
dds(eoe,su)
И действительно, вывод предварительной обработки в обоих примерах одинаковый. Что касается вывода, который вы получаете с VС++, я бы сказал, что вы обнаружили ошибку.
Это согласуется с разделом C99 6.10.3.4, а также с стандартным разделом С++ 16.3.4, повторным сканированием и последующей заменой:
После замены всех параметров в списке замены и # и ## обработка, все маркеры предварительной обработки метки метки помещаются. Затем результирующая повторяющаяся последовательность токенов предварительной обработки, а также все последующие preeccessing tokens исходного файла, для замены других имен макросов.
Ответ 3
Ну, проблема, которую я вижу, заключается в том, что препроцессор выполняет следующие
ddx (x) становится f (x,
Однако f (x, также определяется (даже если он определен как f (a, b)), поэтому f (x, расширяется до x + мусора.
Итак, ddx (x), наконец, преобразуется в x + мусор (потому что вы определили f (smthing,).
Ваш dds (eoe) фактически расширяется в + b, где a - eoe, а b - et_l.
И он делает это дважды по любой причине:).
Этот сценарий, который вы сделали, специфичен для компилятора, зависит от того, как препроцессор решает обработать расширение define.