Стандартный препроцессор 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
#define A 0 \n #define M a ## A
: наличие двух##
не является ключевым. - person Ciro Santilli 新疆再教育营六四事件ۍ   schedule 21.06.2015