Wie kann ich zweimal mit dem C-Präprozessor verketten und ein Makro wie in „arg ## _ ## MACRO“ erweitern?

152

Ich versuche, ein Programm zu schreiben, bei dem die Namen einiger Funktionen vom Wert einer bestimmten Makrovariablen mit einem Makro wie dem folgenden abhängen:

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

int NAME(some_function)(int a);

Leider sind die Makro - NAME()Kurven , dass in

int some_function_VARIABLE(int a);

eher, als

int some_function_3(int a);

Das ist also eindeutig der falsche Weg. Glücklicherweise ist die Anzahl der verschiedenen möglichen Werte für VARIABLE gering, so dass ich einfach #if VARIABLE == nalle Fälle separat auflisten kann , aber ich habe mich gefragt, ob es eine clevere Möglichkeit gibt, dies zu tun.

JJ.
quelle
3
Sind Sie sicher, dass Sie stattdessen keine Funktionszeiger verwenden möchten?
György Andrasek
8
@Jurily - Funktionszeiger arbeiten zur Laufzeit, Präprozessor arbeitet zur (vor) Kompilierungszeit. Es gibt einen Unterschied, auch wenn beide für dieselbe Aufgabe verwendet werden können.
Chris Lutz
1
Der Punkt ist, dass es in einer schnellen rechnergestützten Geometriebibliothek verwendet wird, die für eine bestimmte Dimension fest verdrahtet ist. Manchmal möchte jedoch jemand in der Lage sein, es mit einigen verschiedenen Dimensionen (z. B. 2 und 3) zu verwenden, und daher würde man eine einfache Möglichkeit benötigen, Code mit dimensionsabhängigen Funktions- und Typnamen zu generieren. Außerdem ist der Code in ANSI C geschrieben, sodass das funky C ++ - Zeug mit Vorlagen und Spezialisierung hier nicht anwendbar ist.
JJ.
2
Die Abstimmung zum erneuten Öffnen, da diese Frage spezifisch für die rekursive Makroerweiterung ist und stackoverflow.com/questions/216875/using-in-macros ein generisches "Wofür es gut ist" ist. Der Titel dieser Frage sollte präzisiert werden.
Ciro Santilli 31 冠状 病 六四 事件 31
Ich wünschte, dieses Beispiel wäre minimiert worden: Das gleiche passiert bei #define A 0 \n #define M a ## A: Zwei zu haben ##ist nicht der Schlüssel.
Ciro Santilli 法轮功 冠状 病 六四 事件 21

Antworten:

223

Standard C Präprozessor

$ 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);
$

Zwei Indirektionsebenen

In einem Kommentar zu einer anderen Antwort fragte Cade Roux , warum dies zwei Indirektionsebenen erfordert. Die leichtfertige Antwort lautet, dass der Standard dies so erfordert. Sie neigen dazu, den entsprechenden Trick auch mit dem Stringing-Operator zu benötigen.

Abschnitt 6.10.3 des C99-Standards behandelt "Makroersetzung" und 6.10.3.1 behandelt "Argumentersetzung".

Nachdem die Argumente für den Aufruf eines funktionsähnlichen Makros identifiziert wurden, erfolgt die Argumentersetzung. Ein Parameter in der Ersetzungsliste wird durch das entsprechende Argument ersetzt , sofern nicht ein #oder ein ##Vorverarbeitungstoken oder ein ##Vorverarbeitungstoken (siehe unten) gefolgt ist, nachdem alle darin enthaltenen Makros erweitert wurden. Vor dem Ersetzen werden die Vorverarbeitungstoken jedes Arguments vollständig durch Makros ersetzt, als ob sie den Rest der Vorverarbeitungsdatei bilden würden. Es sind keine weiteren Vorverarbeitungstoken verfügbar.

Im Aufruf NAME(mine)ist das Argument 'meins'; es ist vollständig zu 'mir' erweitert; es wird dann in die Ersatzzeichenfolge eingesetzt:

EVALUATOR(mine, VARIABLE)

Jetzt wird das Makro EVALUATOR entdeckt und die Argumente werden als 'meins' und 'VARIABL' isoliert. Letzteres wird dann vollständig auf '3' erweitert und in die Ersatzzeichenfolge eingesetzt:

PASTER(mine, 3)

Die Funktionsweise wird durch andere Regeln abgedeckt (6.10.3.3 'Der ## Operator'):

Wenn in der Ersetzungsliste eines funktionsähnlichen Makros einem Parameter unmittelbar ein ##Vorverarbeitungstoken vorangestellt oder gefolgt wird , wird der Parameter durch die Vorverarbeitungstokensequenz des entsprechenden Arguments ersetzt. [...]

Sowohl für objektähnliche als auch für funktionsähnliche Makroaufrufe wird jede Instanz eines ##Vorverarbeitungstokens in der Ersetzungsliste (nicht aus einem Argument) gelöscht und das vorhergehende Vorverarbeitungstoken verkettet , bevor die Ersetzungsliste erneut überprüft wird, um weitere Makronamen zu ersetzen mit dem folgenden Vorverarbeitungstoken.

Die Ersatzliste enthält also xgefolgt von ##und ##gefolgt von y; also haben wir:

mine ## _ ## 3

und das Eliminieren der ##Token und das Verketten der Token auf beiden Seiten kombiniert 'meins' mit '_' und '3', um Folgendes zu ergeben:

mine_3

Dies ist das gewünschte Ergebnis.


Wenn wir uns die ursprüngliche Frage ansehen, war der Code (angepasst, um 'meine' anstelle von 'some_function' zu verwenden):

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

NAME(mine)

Das Argument zu NAME ist eindeutig "meins" und das ist vollständig erweitert.
Nach den Regeln von 6.10.3.3 finden wir:

mine ## _ ## VARIABLE

Wenn die ##Operatoren eliminiert sind, werden folgende Karten zugeordnet:

mine_VARIABLE

genau wie in der Frage angegeben.


Traditioneller C-Präprozessor

Robert Rüger fragt :

Gibt es eine Möglichkeit, dies mit dem herkömmlichen C-Präprozessor zu tun, der nicht über den Token-Einfügeoperator verfügt ##?

Vielleicht und vielleicht auch nicht - es hängt vom Präprozessor ab. Einer der Vorteile des Standard-Präprozessors besteht darin, dass er über diese Einrichtung verfügt, die zuverlässig arbeitet, während es für Prä-Standard-Präprozessoren unterschiedliche Implementierungen gab. Eine Anforderung besteht darin, dass der Präprozessor, wenn er einen Kommentar ersetzt, keinen Speicherplatz generiert, wie dies der ANSI-Präprozessor tun muss. Der GCC (6.3.0) C-Präprozessor erfüllt diese Anforderung. Der Clang-Präprozessor aus XCode 8.2.1 funktioniert nicht.

Wenn es funktioniert, erledigt dies den Job ( 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);

Beachten Sie, dass zwischen fun,und kein Leerzeichen VARIABLEsteht. Dies ist wichtig, da es, falls vorhanden, in die Ausgabe kopiert wird und Sie mine_ 3den Namen erhalten, der natürlich syntaktisch nicht gültig ist. (Kann ich jetzt bitte meine Haare zurück haben?)

Mit GCC 6.3.0 (läuft cpp -traditional x-paste.c) bekomme ich:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

Mit Clang von XCode 8.2.1 bekomme ich:

# 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);

Diese Räume verderben alles. Ich stelle fest, dass beide Präprozessoren korrekt sind; Verschiedene Pre-Standard-Präprozessoren zeigten beide Verhaltensweisen, was das Einfügen von Token zu einem äußerst ärgerlichen und unzuverlässigen Prozess machte, wenn versucht wurde, Code zu portieren. Der Standard mit der ##Notation vereinfacht das radikal.

Es gibt möglicherweise andere Möglichkeiten, dies zu tun. Dies funktioniert jedoch nicht:

#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 generiert:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Nah dran, aber keine Würfel. YMMV natürlich abhängig vom verwendeten Preprozessor-Präprozessor. Ehrlich gesagt, wenn Sie mit einem Präprozessor feststecken, der nicht kooperiert, ist es wahrscheinlich einfacher, einen Standard-C-Präprozessor anstelle des Prä-Standard-Präprozessors zu verwenden (es gibt normalerweise eine Möglichkeit, den Compiler entsprechend zu konfigurieren) als zu Verbringen Sie viel Zeit damit, einen Weg zu finden, um die Arbeit zu erledigen.

Jonathan Leffler
quelle
1
Ja, das löst das Problem. Ich kannte den Trick mit zwei Rekursionsstufen - ich musste mindestens einmal mit der Stringifizierung spielen - wusste aber nicht, wie ich diese machen sollte.
JJ.
Gibt es eine Möglichkeit, dies mit dem herkömmlichen C-Präprozessor zu tun, der nicht über den Token-Einfügeoperator ## verfügt?
Robert Rüger
1
@ RobertRüger: Es verdoppelt die Länge der Antwort, aber ich habe Informationen hinzugefügt, um zu decken cpp -traditional. Beachten Sie, dass es keine endgültige Antwort gibt - dies hängt vom Präprozessor ab, den Sie haben.
Jonathan Leffler
Vielen Dank für die Antwort. Das ist total toll! In der Zwischenzeit habe ich auch eine andere, etwas andere Lösung gefunden. Siehe hier . Es hat auch das Problem, dass es mit Clang nicht funktioniert. Zum Glück ist das kein Problem für meine Bewerbung ...
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);

Ehrlich gesagt möchten Sie nicht wissen, warum dies funktioniert. Wenn Sie wissen, warum es funktioniert, werden Sie der Typ bei der Arbeit, der so etwas weiß, und jeder wird kommen und Ihnen Fragen stellen. =)

Bearbeiten: Wenn Sie tatsächlich wissen möchten, warum es funktioniert, werde ich gerne eine Erklärung veröffentlichen, vorausgesetzt, niemand schlägt mich dazu.

Stephen Canon
quelle
Können Sie erklären, warum zwei Indirektionsebenen erforderlich sind? Ich hatte eine Antwort mit einer Umleitungsebene, aber ich habe die Antwort gelöscht, weil ich C ++ in meinem Visual Studio installieren musste und es dann nicht funktionieren würde.
Cade Roux