Стандартний предпроцесор 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);
$
Два рівні непрямості
У коментарі до іншої відповіді Кейд Ру запитав, для чого потрібні два рівні непрямості. Відверта відповідь тому, що саме так стандарт вимагає, щоб він працював; ви схильні знаходити, що вам потрібен рівноцінний трюк і з оператором струнізації.
Розділ 6.10.3 стандарту C99 охоплює "заміну макросів", а 6.10.3.1 - "заміну аргументів".
Після виявлення аргументів для виклику функціонального макросу відбувається підміна аргументів. Параметр у списку заміни, за винятком випадків, коли маркер #
або ##
попередній обробці або після нього ##
маркер попередньої обробки (див. Нижче) замінюється відповідним аргументом після того, як усі макроси, що містяться в ньому, були розширені. Перед тим, як замінити, лексеми попередньої обробки кожного аргументу повністю замінюються макрокомандами, ніби вони утворюють решту файлу попередньої обробки; ніяких інших токенів попередньої обробки немає.
У виклику NAME(mine)
аргумент є "мій"; вона повністю розширена на «моє»; Потім він заміщується в рядок заміни:
EVALUATOR(mine, VARIABLE)
Тепер макрос ОЦІНЮВАННЯ виявлено, а аргументи виділяються як "моє" та "ЗМІННЕ"; остання потім повністю розширюється до '3' і замінюється в рядок заміни:
PASTER(mine, 3)
Дія цього регулюється іншими правилами (6.10.3.3 'Оператор ##'):
Якщо у списку заміни функціонального макросу параметру негайно передує або слідує ##
маркер попередньої обробки, параметр заміняється відповідною послідовністю лексеми попередньої обробки аргументу; [...]
Як для об'єктних, так і функціональних викликів макросів, перед тим, як список заміни буде переглянуто, щоб замінити більше імен макросів, кожен екземпляр ##
маркера попередньої обробки зі списку заміни (не з аргументу) видаляється, а попередній маркер попередньої обробки об'єднується із наступним маркером попередньої обробки.
Отже, список заміни містить, x
за яким слідує, ##
а також ##
слідує y
; тому у нас є:
mine ## _ ## 3
і усунення ##
жетонів і об'єднання лексем з обох сторін поєднує в собі "міну" з "_" і "3", щоб отримати:
mine_3
Це бажаний результат.
Якщо ми подивимось на початкове запитання, код був (пристосований для використання "мій" замість "деякої функції"):
#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 замість попереднього стандарту (як правило, спосіб налаштувати компілятор належним чином), ніж витратити багато часу, намагаючись розробити спосіб зробити роботу.