Я пытаюсь определить проблему из-за необычного использования variadic макросов. Вот гипотетический макрос:
#define va(c, d, ...) c(d, __VA_ARGS__)
#define var(a, b, ...) va(__VA_ARGS__, a, b)
var(2, 3, printf, “%d %d %d\n”, 1);
Для gcc препроцессор произведет
printf("%d %d %d\n", 1, 2, 3)
но для VS 2008, вывод
printf, “%d %d %d\n”, 1(2, 3);
Я подозреваю, что различие вызывается другим отношением к VA_ARGS для gcc, это сначала развернет выражение до va (printf, "%d %d %d\n", 1, 2, 3), и рассматривайте 1, 2, 3 как VA_ARGS для макроса va. Но для VS 2008, это будет сначала рассматривать b как VA_ARGS для макроса va и затем делать расширение.
Какой является корректной интерпретацией для макроса C99 variadic? или мое использование попадает в неопределенное поведение?
Загляните в ISO/IEC 9899:1999, глава 6.10.3.1. Там говорится, что:
После того, как аргументы для вызова функционально-подобного макроса были определены, происходит подстановка аргументов. Параметр в списке замены, если ему не предшествует препроцессинговая лексема # или ## или за ним не следует препроцессинговая лексема ## (см. ниже), заменяется соответствующим аргументом после того, как все содержащиеся в нем макросы были развернуты. Перед заменой, препроцессинговые лексемы каждого аргумента полностью заменяются макросами, как если бы они составляли остальную часть файла препроцессирования; никакие другие препроцессинговые лексемы не доступны.
Так в va первый аргумент c имеет один токен препроцессирования - __VA_ARGS__
, который, согласно этому параграфу, должен быть макрозаменен перед подстановкой в c (что по-прежнему не дает ответа на вопрос, какой компилятор прав)
Но далее:
Идентификатор
__VA_ARGS__
, который встречается в списке замены должен рассматриваться как параметр, а переменные аргументы должны формировать лексемы предварительной обработки, используемые для замены.
Согласно первому фрагменту, сначала определяются аргументы var. Ими являются: 2
, 3
, printf
, "%d %d %d\n"
и 1
. Теперь происходит подстановка аргументов. Это означает, что параметры из списка замены var берутся и заменяются. Однако во втором фрагменте указано, что идентификатор __VA_ARGS__
должен рассматриваться как параметр, поэтому он должен быть заменен на printf, "%d %d %d\n", 1
. Теперь внутри параметров нет макросов, поэтому дальнейшей подстановки не происходит, в результате чего var(2, 3, printf, "%d %d %d\n", 1);
расширяется до va(printf, "%d %d %d\n", 1, 2, 3);
. Поскольку va является макросом, он также расширяется, давая результат printf("%d %d %d\n", 1, 2, 3);
.
Теперь, если принять рассуждения VS 2008, как она может определить аргументы для va, если один из них __VA_ARGS__
из var, и может содержать много аргументов? Ну, он рассматривает __VA_ARGS__
как аргумент к va, что, на мой взгляд, неверно, поскольку, согласно первому фрагменту, подстановка аргументов происходит только после определения аргументов для вызова.
Поэтому мне кажется, что в var препроцессор должен сначала определить аргументы для вызова макроса va, а затем начать расширение va. А это значит, что, вероятно, gcc здесь прав.
Также в C preprocessor and concatenation показано, как обрабатывающие лексемы заменяются макросами по порядку, пока не останется идентификаторов, которые могут быть далее расширены как макросы, или препроцессор обнаружит рекурсию и прекратит расширение.