Standardowy preprocesor 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);
$
Dwa poziomy pośrednictwa
W komentarzu do innej odpowiedzi Cade Roux zapytał, dlaczego potrzebne są dwa poziomy pośrednictwa. Lekceważąca odpowiedź jest taka, ponieważ tego wymaga norma, aby działała; zwykle okazuje się, że potrzebujesz podobnej sztuczki z operatorem naciągania.
Sekcja 6.10.3 standardu C99 dotyczy „zastępowania makr”, a 6.10.3.1 - „zastępowania argumentów”.
Po zidentyfikowaniu argumentów wywołania makra podobnego do funkcji następuje podstawianie argumentów. Parametr na liście zastępczej, chyba że jest poprzedzony tokenem przetwarzania wstępnego #
lub ##
tokenem przetwarzania wstępnego lub następuje po nim ##
token przetwarzania wstępnego (patrz poniżej), jest zastępowany odpowiednim argumentem po rozwinięciu wszystkich zawartych w nim makr. Przed podstawieniem tokeny przetwarzania wstępnego każdego argumentu są całkowicie zastępowane makrami, tak jakby tworzyły resztę pliku przetwarzania wstępnego; żadne inne tokeny przetwarzania wstępnego nie są dostępne.
W inwokacji NAME(mine)
argumentem jest „mój”; jest w pełni rozszerzony na „mój”; jest następnie podstawiany w łańcuch zastępujący:
EVALUATOR(mine, VARIABLE)
Teraz makro EVALUATOR jest odkrywane, a argumenty są izolowane jako „moje” i „ZMIENNE”; ta ostatnia jest następnie w pełni rozwijana do „3” i zastępowana w zastępczym ciągu:
PASTER(mine, 3)
Działanie tego jest objęte innymi zasadami (6.10.3.3 „Operator ##”):
Jeśli na liście zastępczej makra podobnego do funkcji parametr jest bezpośrednio poprzedzony lub zakończony ##
tokenem przetwarzania wstępnego, parametr jest zastępowany sekwencją tokenu przetwarzania wstępnego odpowiedniego argumentu; […]
W przypadku wywołań makr zarówno obiektowych, jak i funkcyjnych, przed ponownym zbadaniem listy zastępczej pod kątem większej liczby nazw makr do zastąpienia, każde wystąpienie ##
tokenu przetwarzania wstępnego na liście zastępczej (nie z argumentu) jest usuwane, a poprzedzający token przetwarzania wstępnego jest łączony z następującym tokenem przetwarzania wstępnego.
Tak, lista zawiera wymiana x
następuje ##
, a także ##
następuje y
; więc mamy:
mine ## _ ## 3
a eliminacja ##
tokenów i łączenie tokenów po obu stronach łączy „moje” z „_” i „3”, aby uzyskać:
mine_3
To jest pożądany rezultat.
Jeśli spojrzymy na oryginalne pytanie, kod był (dostosowany do używania „mine” zamiast „some_function”):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
Argument do NAZWY jest wyraźnie „mój” i jest w pełni rozwinięty.
Zgodnie z zasadami 6.10.3.3 znajdujemy:
mine ## _ ## VARIABLE
która po ##
wyeliminowaniu operatorów odwzorowuje:
mine_VARIABLE
dokładnie tak, jak podano w pytaniu.
Tradycyjny preprocesor C.
Robert Rüger pyta :
Czy można to zrobić z tradycyjnym preprocesorem C, który nie ma operatora wklejania tokenów ##
?
Może, a może nie - zależy to od preprocesora. Jedną z zalet standardowego preprocesora jest to, że ma tę funkcję, która działa niezawodnie, podczas gdy istniały różne implementacje preprocesorów przed standardem. Jednym z wymagań jest to, że gdy preprocesor zastępuje komentarz, nie generuje spacji, jak jest to wymagane przez preprocesor ANSI. Preprocesor C GCC (6.3.0) spełnia to wymaganie; preprocesor Clang z XCode 8.2.1 tego nie robi.
Kiedy to działa, wykonuje to zadanie ( 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);
Zwróć uwagę, że nie ma spacji między fun,
a VARIABLE
- to ważne, ponieważ jeśli jest obecny, jest kopiowany na wyjście, a kończy się mine_ 3
na nazwie, która oczywiście nie jest poprawna składniowo. (Teraz, proszę, czy mogę odzyskać włosy?)
Z GCC 6.3.0 (uruchomionym cpp -traditional x-paste.c
) otrzymuję:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_3(char *x);
Dzięki Clang z XCode 8.2.1 otrzymuję:
# 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);
Te przestrzenie psują wszystko. Zauważam, że oba preprocesory są poprawne; różne preprocesory przed standardem wykazywały oba zachowania, co powodowało, że wklejanie tokenów było niezwykle denerwującym i zawodnym procesem podczas próby przeniesienia kodu. Norma z ##
notacją radykalnie to upraszcza.
Mogą istnieć inne sposoby, aby to zrobić. Jednak to nie działa:
#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 generuje:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
Blisko, ale bez kostek. YMMV oczywiście w zależności od preprocesora preprocesora, którego używasz. Szczerze mówiąc, jeśli utkniesz z preprocesorem, który nie współpracuje, prawdopodobnie prościej byłoby zaaranżować użycie standardowego preprocesora C zamiast preprocesora standardowego (zwykle jest sposób na odpowiednie skonfigurowanie kompilatora) niż spędzać dużo czasu próbując znaleźć sposób na wykonanie tej pracy.