Стандартный 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);
$
Два уровня косвенности
В комментарии к другому ответу Кейд Ру спросил, почему для этого нужны два уровня косвенности. Непростой ответ: потому что так требует стандарт для работы; вы также обнаружите, что вам нужен эквивалентный трюк с оператором 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
и удаление ##
токенов и объединение токенов с обеих сторон объединяет «мой» с «_» и «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 вместо предстандартного (обычно есть способ настроить компилятор соответствующим образом), чем тратить много времени, пытаясь найти способ сделать работу.