Как дважды объединить с препроцессором C и развернуть макрос как в "arg ## _ ## MACRO"?

Я пытаюсь написать программу, в которой имена некоторых функций зависят от значения определенной макропеременной с помощью макроса, например:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

К сожалению, макрос NAME() превращает это в

int some_function_VARIABLE(int a);

скорее, чем

int some_function_3(int a);

так что это явно неправильный путь. К счастью, число различных возможных значений для VARIABLE мало, поэтому я могу просто сделать #if VARIABLE == n и перечислить все случаи отдельно, но мне было интересно, есть ли умный способ сделать это.

2 ответа

Стандартный препроцессор C

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Два уровня косвенности

В комментарии к другому ответу Cade Roux спросил, почему для этого нужны два уровня косвенности. Непростой ответ - потому что так требует стандарт для работы; вы склоняетесь к тому, что вам нужен эквивалентный трюк с оператором stringizing.

Раздел 6.10.3 стандарта C99 охватывает "замену макросов", а 6.10.3.1 - "замену аргументов".

После того, как аргументы для вызова функционально-подобного макроса были идентифицированы, происходит подстановка аргументов. Параметр в списке замен, если ему не предшествует # или же ## токен предварительной обработки или сопровождается ## Токен предварительной обработки (см. ниже) заменяется соответствующим аргументом после раскрытия всех содержащихся в нем макросов. Перед заменой токены предварительной обработки каждого аргумента полностью заменяются макросами, как если бы они образовывали остальную часть файла предварительной обработки; другие токены предварительной обработки недоступны.

В вызове NAME(mine)аргумент "мой"; оно полностью расширено до "моего"; затем он подставляется в строку замены:

EVALUATOR(mine, VARIABLE)

Теперь макрос EVALUATOR обнаружен, а аргументы изолированы как "моя" и "переменная"; последний затем полностью расширяется до '3' и подставляется в строку замены:

PASTER(mine, 3)

Операция этого покрыта другими правилами (6.10.3.3 "Оператор ##"):

Если в списке замены функционально-подобного макроса параметру сразу предшествует или следует ## токен предварительной обработки, параметр заменяется последовательностью токена предварительной обработки соответствующего аргумента; [...]

Как для объектно-подобных, так и для функционально-подобных вызовов макросов, перед пересмотром списка замен для замены новых имен макросов, каждый экземпляр ## Токен предварительной обработки в списке замены (не из аргумента) удаляется, а предыдущий токен предварительной обработки объединяется со следующим токеном предварительной обработки.

Итак, список замены содержит x с последующим ## а также ## с последующим y; итак имеем:

mine ## _ ## 3

и устранение ## токены и конкатенация токенов с обеих сторон объединяет 'mine' с '_' и '3', чтобы получить:

mine_3

Это желаемый результат.


Если мы посмотрим на исходный вопрос, код был (адаптирован для использования 'mine' вместо 'some_function'):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

Аргумент NAME явно "мой", и он полностью расширен.
Следуя правилам 6.10.3.3, находим:

mine ## _ ## VARIABLE

который, когда ## операторы исключены, карты для:

mine_VARIABLE

именно так, как сообщается в вопросе.


Традиционный препроцессор C

Robert Rüger спрашивает:

Есть ли способ сделать это с традиционным препроцессором C, который не имеет оператора вставки токена ##?

Может быть, а может и нет - это зависит от препроцессора. Одно из преимуществ стандартного препроцессора заключается в том, что он имеет эту функцию, которая работает надежно, тогда как для предстандартных препроцессоров были разные реализации. Одно требование состоит в том, что, когда препроцессор заменяет комментарий, он не генерирует пробел, как это требуется для препроцессора ANSI. Препроцессор GCC (6.3.0) C отвечает этому требованию; препроцессор Clang из XCode 8.2.1 этого не делает.

Когда это работает, это делает работу (x-paste.c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Обратите внимание, что между fun, а также VARIABLE - это важно, потому что если он присутствует, он копируется в вывод, и вы в конечном итоге mine_ 3 как имя, которое не является синтаксически допустимым, конечно. (Теперь, пожалуйста, можно мне вернуть волосы?)

С GCC 6.3.0 (работает cpp -traditional x-paste.c), Я получил:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

С Clang из XCode 8.2.1 я получаю:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Эти пространства все портят. Я отмечаю, что оба препроцессора верны; различные предстандартные препроцессоры демонстрировали оба поведения, что делало вставку токена чрезвычайно раздражающим и ненадежным процессом при попытке переноса кода. Стандарт с ## запись в корне упрощает это.

Там могут быть другие способы сделать это. Однако это не работает:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC генерирует:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Близко, но без игры в кости. YMMV, конечно, в зависимости от используемого вами стандартного препроцессора. Честно говоря, если вы застряли с препроцессором, который не взаимодействует, вероятно, было бы проще организовать использование стандартного препроцессора C вместо предстандартного (обычно есть способ настроить компилятор соответствующим образом), чем тратить много времени, пытаясь найти способ сделать работу.

#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Честно говоря, вы не хотите знать, почему это работает. Если вы знаете, почему это работает, вы станете тем парнем на работе, который знает подобные вещи, и все придут, чтобы задать вам вопросы. знак равно

Редактировать: если вы действительно хотите знать, почему это работает, я с удовольствием выложу объяснение, предполагая, что никто меня не побьет.

Простое английское объяснение EVALUATOR двухступенчатый узор

Я не полностью понял каждое слово стандарта C, но я думаю, что это разумная рабочая модель для того, как работает решение, показанное в /questions/18714031/kak-dvazhdyi-obedinit-s-preprotsessorom-c-i-razvernut-makros-kak-v-arg-macro/18714039#18714039, объясненное немного более подробно. Сообщите мне, если мое понимание неверно, надеюсь, с минимальным примером, который нарушает мою теорию.

Для наших целей мы можем рассматривать расширение макроса как происходящее в три этапа:

  1. (предварительное сканирование) Аргументы макроса заменяются:
    • если они являются частью конкатенации или строкового преобразования, они заменяются точно так же, как строка, указанная в вызове макроса, без раскрытия
    • в противном случае они сначала полностью раскрываются, а только потом заменяются
  2. Стренификация и конкатенация происходят
  3. Все определенные макросы раскрываются

Пошаговый пример без косвенного обращения

main.c

#define CAT(x) pref_ ## x
#define Y a

CAT(Y)

и расширите его с помощью:

gcc -E main.c

мы получили:

pref_Y

потому как:

Шаг 1: Y является макро-аргументом CAT.

x появляется в строке pref_ ## x. Следовательно, Y вставляется как есть без предоставления расширения:

pref_ ## Y

Шаг 2: происходит конкатенация, и у нас остается:

pref_Y

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

Мы можем подтвердить эту теорию, добавив определение к pref_Y:

#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf

CAT(Y)

и теперь результат будет:

asdf

потому что на шаге 3 выше pref_Y не определяется как макрос и поэтому расширяется.

Пошаговый пример с косвенным обращением

Однако, если мы воспользуемся двухступенчатым шаблоном:

#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a

CAT(Y)

мы получили:

pref_a

Шаг 1: CATоценивается. Аргумент x из CAT в CAT2(x)не появляется в стрингификации. Следовательно, Y полностью расширяется перед заменой, пройдя шаги 1, 2 и 3, которые мы опускаем здесь, потому что он тривиально расширяется до a. Итак, мы положили a в CAT2(x) давая:

CAT2(a)

Шаг 2: не нужно делать никаких строк

Шаг 3: разверните все существующие макросы. У нас есть макрос CAT2(a) и поэтому мы продолжаем расширять это.

Шаг 3.1: аргумент x из CAT2 появляется в строке pref_ ## x. Поэтому вставьте строку ввода a как есть, давая:

pref_ ## a

Шаг 3.2: преобразовать в строку:

pref_a

Шаг 3: разверните дополнительные макросы. pref_a это не какой-либо макрос, поэтому мы закончили.

Документация по предварительному сканированию аргументов GCC

Также стоит прочитать документацию GCC по этому вопросу: https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

Бунус: как эти правила предотвращают бесконечное количество вложенных вызовов

Теперь рассмотрим:

#define f(x) (x + 1)

f(f(a))

который расширяется до:

((a + 1) + 1)

вместо бесконечности.

Давайте разберемся:

Шаг 1: внешний f вызывается с аргументом x = f(a).

В определении f, Аргумент x не является частью конкатенации в определении (x + 1) из f. Поэтому перед заменой его сначала полностью расширяют.

Шаг 1.1: полностью раскрываем аргумент x = f(1) согласно шагам 1, 2 и 3, давая x = (a + 1).

Вернувшись к шагу 1, мы берем полностью развернутый x аргумент, равный (a + 1), и поместите его в определение f давая:

((a + 1) + 1)

Шаги 2 и 3: мало что происходит, потому что у нас нет строкового преобразования и нет макросов для расширения.

Другие вопросы по тегам