Как дважды объединить с препроцессором 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
Есть ли способ сделать это с традиционным препроцессором 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, объясненное немного более подробно. Сообщите мне, если мое понимание неверно, надеюсь, с минимальным примером, который нарушает мою теорию.
Для наших целей мы можем рассматривать расширение макроса как происходящее в три этапа:
- (предварительное сканирование) Аргументы макроса заменяются:
- если они являются частью конкатенации или строкового преобразования, они заменяются точно так же, как строка, указанная в вызове макроса, без раскрытия
- в противном случае они сначала полностью раскрываются, а только потом заменяются
- Стренификация и конкатенация происходят
- Все определенные макросы раскрываются
Пошаговый пример без косвенного обращения
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: мало что происходит, потому что у нас нет строкового преобразования и нет макросов для расширения.