Как дважды выполнить конкатенацию с препроцессором 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 и перечислить все случаи отдельно, но мне было интересно, есть ли умный способ сделать это.


person JJ.    schedule 29.09.2009    source источник
comment
Вы уверены, что не хотите использовать вместо них указатели на функции?   -  person György Andrasek    schedule 29.09.2009
comment
@Jurily - указатели на функции работают во время выполнения, препроцессор работает во время (до) компиляции. Есть разница, даже если оба могут использоваться для одной и той же задачи.   -  person Chris Lutz    schedule 29.09.2009
comment
Дело в том, что она используется в библиотеке быстрой вычислительной геометрии ... которая жестко привязана к определенному измерению. Однако иногда кто-то может захотеть использовать его с несколькими разными измерениями (скажем, 2 и 3), и поэтому потребуется простой способ сгенерировать код с функциями и типами, зависящими от размера. Кроме того, код написан на ANSI C, поэтому забавный материал C ++ с шаблонами и специализацией здесь не применим.   -  person JJ.    schedule 29.09.2009
comment
Голосование за повторное открытие, потому что этот вопрос касается рекурсивного расширения макросов и stackoverflow.com/questions/216875/using-in-macros является универсальным, для чего он нужен. Название этого вопроса следует уточнить.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 01.06.2015
comment
Я бы хотел, чтобы этот пример был минимизирован: то же самое происходит с #define A 0 \n #define M a ## A: наличие двух ## не является ключевым.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 21.06.2015


Ответы (3)


Стандартный препроцессор 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 спросил, зачем для этого нужны два уровня косвенного обращения. Легкомысленный ответ заключается в том, что стандарт требует, чтобы это работало; вы, как правило, обнаруживаете, что вам нужен аналогичный трюк и с оператором преобразования строк.

Раздел 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 вместо предстандартного (обычно есть способ настроить компилятор соответствующим образом), чем использовать тратьте много времени на то, чтобы найти способ выполнить работу.

person Jonathan Leffler    schedule 29.09.2009
comment
Да, это решает проблему. Я знал трюк с двумя уровнями рекурсии - мне приходилось играть со стрингификацией по крайней мере один раз - но не знал, как это сделать. - person JJ.; 29.09.2009
comment
Есть ли способ сделать это с помощью традиционного препроцессора C, который делает нет оператора вставки токена ##? - person Robert Rüger; 24.12.2016
comment
@ RobertRüger: он удваивает длину ответа, но я добавил информацию, чтобы охватить cpp -traditional. Обратите внимание, что однозначного ответа нет - это зависит от препроцессора, который у вас есть. - person Jonathan Leffler; 24.12.2016
comment
Большое спасибо за ответ. Это здорово! Тем временем я также нашел другое, немного другое решение. См. здесь. Но у него также есть проблема, что он не работает с clang. К счастью, для моего приложения это не проблема ... - person Robert Rüger; 26.12.2016

#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);

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

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

person Stephen Canon    schedule 29.09.2009
comment
Не могли бы вы объяснить, зачем нужны два уровня косвенного обращения. У меня был ответ с одним уровнем перенаправления, но я удалил ответ, потому что мне пришлось установить C ++ в мою Visual Studio, и тогда он не работал. - person Cade Roux; 29.09.2009

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

Я не полностью понял каждое слово стандарта C, но я думаю, что это разумная рабочая модель для решения, показанного в https://stackoverflow.com/a/1489985/895245 работает, объяснено немного более подробно. Сообщите мне, если мое понимание неверно, надеюсь, с минимальным примером, который нарушает мою теорию.

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

  1. (prescan) Macro arguments are replaced:
    • if they are part of concatenation or stringification, they are replaced exactly as the string given on the macro call, without being expanded
    • в противном случае они сначала полностью раскрываются, а только потом заменяются
  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 оценивается.

CAT(x) определен как CAT2(x), поэтому аргумент x из CAT в определении не появляется в строковой модификации: строковая привязка происходит только после того, как CAT2 раскрывается, что не видно на этом шаге.

Следовательно, 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: мало что происходит, потому что у нас нет строковой привязки и больше нет макросов для расширения.

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 03.11.2020