Jak dwukrotnie połączyć z preprocesorem C i rozwinąć makro, jak w „arg ## _ ## MACRO”?

152

Próbuję napisać program, w którym nazwy niektórych funkcji są zależne od wartości określonej zmiennej makra z makrem takim jak to:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Niestety, makro NAME()zamienia to na

int some_function_VARIABLE(int a);

zamiast

int some_function_3(int a);

więc jest to zdecydowanie niewłaściwy sposób postępowania. Na szczęście liczba różnych możliwych wartości zmiennej VARIABLE jest niewielka, więc mogę po prostu zrobić #if VARIABLE == ni wymienić wszystkie przypadki osobno, ale zastanawiałem się, czy istnieje sprytny sposób, aby to zrobić.

JJ.
źródło
3
Czy na pewno nie chcesz zamiast tego używać wskaźników funkcji?
György Andrasek,
8
@Jurily - Wskaźniki funkcji działają w czasie wykonywania, preprocesor działa w (przed) czasie kompilacji. Jest różnica, nawet jeśli oba mogą być używane do tego samego zadania.
Chris Lutz,
1
Chodzi o to, że to, w czym jest używana, to szybka biblioteka geometrii obliczeniowej ... która jest wbudowana w określony wymiar. Czasami jednak ktoś chciałby móc go używać z kilkoma różnymi wymiarami (powiedzmy 2 i 3), więc potrzebny byłby łatwy sposób na wygenerowanie kodu z funkcją zależną od wymiaru i nazwami typów. Ponadto kod jest napisany w ANSI C, więc funky C ++ z szablonami i specjalizacją nie ma tutaj zastosowania.
JJ.
2
Głosowanie za ponownym otwarciem, ponieważ to pytanie jest specyficzne dla rekurencyjnego rozwijania makr i stackoverflow.com/questions/216875/using-in-macros to ogólne „do czego to jest dobre”. Tytuł tego pytania powinien być sprecyzowany.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Żałuję, że ten przykład nie został zminimalizowany: to samo dzieje się na #define A 0 \n #define M a ## A: posiadanie dwóch ##nie jest kluczem.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

223

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 xnastę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_ 3na 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.

Jonathan Leffler
źródło
1
Tak, to rozwiązuje problem. Znałem sztuczkę z dwoma poziomami rekurencji - przynajmniej raz musiałem zagrać ze stringifikacją - ale nie wiedziałem, jak to zrobić.
JJ.
Czy można to zrobić z tradycyjnym preprocesorem C, który nie ma operatora wklejania tokenów ##?
Robert Rüger
1
@ RobertRüger: podwaja długość odpowiedzi, ale dodałem dodatkowe informacje cpp -traditional. Zauważ, że nie ma ostatecznej odpowiedzi - zależy to od preprocesora, który masz.
Jonathan Leffler
Bardzo dziękuję za odpowiedź. To jest super! W międzyczasie znalazłem też inne, nieco inne rozwiązanie. Zobacz tutaj . Ma również problem z tym, że nie działa z brzękiem. Na szczęście nie dotyczy to mojej aplikacji ...
Robert Rüger
32
#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);

Szczerze mówiąc, nie chcesz wiedzieć, dlaczego to działa. Jeśli wiesz, dlaczego to działa, staniesz się w pracy tym facetem, który zna się na takich rzeczach, i każdy przyjdzie zadać ci pytania. =)

Edycja: jeśli naprawdę chcesz wiedzieć, dlaczego to działa, z radością opublikuję wyjaśnienie, zakładając, że nikt mnie nie przebije.

Stephen Canon
źródło
Czy mógłbyś wyjaśnić, dlaczego potrzebuje dwóch poziomów pośrednich. Otrzymałem odpowiedź z jednym poziomem przekierowania, ale usunąłem odpowiedź, ponieważ musiałem zainstalować C ++ w moim programie Visual Studio i wtedy to nie zadziała.
Cade Roux,