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