Удивительное расширение макроса препроцессора variadi c GNU C при наличии оператора ## - PullRequest
4 голосов
/ 14 февраля 2020

Если мы определим макрос

#define M(x, ...) { x, __VA_ARGS__ }

и затем используем его в качестве аргумента

M(M(1, 2), M(3, 4), M(5, 6))

, то он расширяется до ожидаемой формы:

{ { 1, 2 }, { 3, 4 }, { 5, 6 } }

Однако, когда мы используем оператор ## (для предотвращения появления висячей запятой в выходных данных в случае вызовов с одним аргументом, как описано в руководстве G CC ), т.е. 1013 *

#define M0(x, ...) { x, ## __VA_ARGS__ }

тогда расширение аргументов в

M0(M0(1,2), M0(3,4), M0(5,6))

кажется остановленным после первого аргумента, то есть мы получаем:

{ { 1,2 }, M0(3,4), M0(5,6) }

Является ли это поведение ошибкой, или это проистекает из какого-то принципа?

(я также проверил его с помощью clang, и он ведет себя так же, как G CC)

1 Ответ

5 голосов
/ 16 февраля 2020

В конце этого ответа есть возможное решение.

Является ли это поведение ошибкой или оно проистекает из какого-то принципа?

Это вытекает из двух принципов, взаимодействие которых довольно тонкое. Поэтому я согласен, что это удивительно, но это не ошибка.

Два принципа следующие:

  1. Внутри замены вызова макроса этот макрос не является расширен. (См. G CC Руководство, раздел 3.10.5, Само-ссылочные макросы или C Стандарт, §6.10.3.4, параграф 2.) Это исключает рекурсивное расширение макроса, которое в большинстве случаев приводит к бесконечная рекурсия, если это разрешено. Хотя, вероятно, никто не ожидал такого использования, оказывается, что были бы способы использования рекурсивного расширения макроса, которые не привели бы к бесконечной рекурсии (см. Документацию Boost Preprocessor Library для подробное обсуждение этого вопроса ), но стандарт не изменится сейчас.

  2. Если к аргументу макроса применяется ##, то он подавляет расширение макроса этого аргумента. (См. G CC Руководство, раздел 3.5, Объединение или C Стандарт, §6.10.3.3, параграф 2.) Подавление расширения является частью C стандарта, но GCC / Clang расширение, позволяющее использовать ## для условного подавления запятой, предшествующей __VA_ARGS__, является нестандартным. (См. G CC Руководство, раздел 3.6, Variadi c Макросы .) Очевидно, расширение все еще соблюдает правило стандарта о том, что не нужно расширять конкатенированные макро-аргументы.

Теперь, во втором пункте, касающемся необязательного подавления запятых, любопытно то, что вы практически не замечаете этого на практике. Вы можете использовать ## для условного подавления запятых, и аргументы по-прежнему будут расширяться как обычно:

#define SHOW_ARGS(arg1, ...) Arguments are (arg1, ##__VA_ARGS__)
#define DOUBLE(a) (2 * a)
SHOW_ARGS(DOUBLE(2))
SHOW_ARGS(DOUBLE(2), DOUBLE(3))

Это расширяется до:

Arguments are ((2 * 2))
Arguments are ((2 * 2), (2 * 3))

Оба DOUBLE(2) и DOUBLE(3) являются обычно расширяется, несмотря на то, что один из них является аргументом оператора конкатенации.

Но есть и тонкость в расширении макроса. Расширение происходит дважды:

  1. Сначала раскрываются аргументы макроса. (Это расширение находится в контексте текста, который вызывает макрос.) Эти расширенные аргументы заменяются параметрами в теле замены макроса (но только в том случае, если параметр не является аргументом # или ##).

  2. Затем операторы # и ## применяются к списку токенов замены.

  3. Наконец, полученные токены замены вставляются в входной поток, чтобы они снова расширились. На этот раз расширение происходит в контексте макроса, поэтому рекурсивный вызов подавляется.

Имея это в виду, мы видим, что в SHOW_ARGS(DOUBLE(2), DOUBLE(3)), DOUBLE(2) раскрывается на шаге 1, перед вставкой в ​​список токенов замены, и DOUBLE(3) расширяется на шаге 3 как часть списка токенов замены.

Это не имеет значения с DOUBLE внутри SHOW_ARGS , поскольку они разные макросы. Но разница станет очевидной, если они будут одним и тем же макросом.

Чтобы увидеть разницу, рассмотрим следующий макрос:

#define INVOKE(A, ...) A(__VA_ARGS__)

Этот макрос создает вызов макроса (или вызов функции, но здесь нас интересует только тот случай, когда это макрос). То есть по очереди INVOKE(X, Y) в X(Y). (Это упрощение полезной функции, когда названный макрос на самом деле вызывается несколько раз, возможно, с несколько иными аргументами.)

Это прекрасно работает с SHOW_ARGS:

INVOKE(SHOW_ARGS, one arg)

⇒ Arguments are (one arg)

Но если мы попытаемся INVOKE сам макрос INVOKE, мы обнаружим, что запрет на рекурсивный вызов вступает в силу:

INVOKE(INVOKE, SHOW_ARGS, one arg)

⇒ INVOKE(SHOW_ARGS, one arg)

«Конечно», мы можем расширить INVOKE в качестве аргумента для INVOKE:

INVOKE(SHOW_ARGS, INVOKE(SHOW_ARGS, one arg))

⇒ Arguments are (Arguments are (one arg))

Это прекрасно работает, потому что внутри INVOKE нет ##, поэтому расширение аргумента не подавляется. Но если расширение аргумента было подавлено, то аргумент будет вставлен в тело макроса без расширения, и тогда он станет рекурсивным расширением.

Так вот, что происходит в вашем примере:

#define M0(x, ...) { x, ## __VA_ARGS__ }
M0(M0(1,2), M0(3,4), M0(5,6))

⇒ { { 1,2 }, M0(3,4), M0(5,6) }

Здесь первый аргумент для внешнего M0, M0(1,2) не используется с ##, поэтому он раскрывается как часть вызова. Два других аргумента являются частью __VA_ARGS__, который используется с ##. Следовательно, они не раскрываются перед заменой в список замены макроса. Но как часть списка замены макроса, его расширение подавляется правилом no-recursive-macro,

Вы можете легко обойти это, определив две версии макроса M0 с одинаковым содержимым, но разные имена (как предложено в комментарии к OP):

#define M0(x, ...) { x, ## __VA_ARGS__ }
M0(M1(1,2), M1(3,4), M1(5,6))

⇒ { { 1,2 }, { 3,4 }, { 5,6 } }

Но это не очень приятно.

Решение: Используйте __VA_OPT__

C ++ 2a будет включать новую функцию, специально разработанную для подавления запятых в вызовах variadi c: функциональный макрос __VA_OPT__. Внутри расширения макроса variadi c, __VA_OPT__(x) расширяется до своего аргумента, при условии, что в аргументах variadi c есть хотя бы один токен. Но если __VA_ARGS__ расширяется до пустого списка токенов, то и __VA_OPT__(x). Таким образом, __VA_OPT__(,) может использоваться для условного подавления запятой точно так же, как расширение G CC ##, но в отличие от ##, оно не вызывает подавления расширения макроса.

В качестве расширения стандарта C последние версии G CC и Clang реализуют __VA_OPT__ для C, а также C ++. (См. G CC Руководство, раздел 3.6, Variadi c Макросы .) Так что, если вы хотите положиться на относительно недавние версии компилятора, есть очень чистое решение:

#define M0(x, ...) { x __VA_OPT__(,) __VA_ARGS__ }
M0(M0(1,2), M0(3,4), M0(5,6))

⇒ { { 1 , 2 } , { 3 , 4 }, { 5 , 6 } }

Примечания:

  1. Вы можете увидеть эти примеры на Godbolt

  2. Этот вопрос был изначально задан закрыто как копия макроса Variadi c: расширение вставленных токенов , но я не думаю, что этот ответ действительно адекватен этой конкретной ситуации.

...