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

Lesezeit: 10 Minuten

Benutzeravatar von JJ
JJ.

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

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

int NAME(some_function)(int a);

Leider das Makro NAME() verwandelt das in

int some_function_VARIABLE(int a);

statt

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, sodass ich einfach eine machen kann #if VARIABLE == n und alle Fälle separat auflisten, aber gibt es einen cleveren Weg, dies zu tun?

  • Sind Sie sicher, dass Sie stattdessen keine Funktionszeiger verwenden möchten?

    – György Andrasek

    29. September 2009 um 0:26 Uhr

  • @Jurily – Funktionszeiger arbeiten zur Laufzeit, Präprozessor arbeitet zur (vor) Kompilierzeit. Es gibt einen Unterschied, auch wenn beide für die gleiche Aufgabe verwendet werden können.

    – Chris Lutz

    29. September 2009 um 0:29 Uhr

  • Der Punkt ist, dass es in einer schnellen Berechnungsgeometriebibliothek verwendet wird, die für eine bestimmte Dimension fest verdrahtet ist. Manchmal möchte jedoch jemand in der Lage sein, es mit ein paar verschiedenen Dimensionen (z. B. 2 und 3) zu verwenden, und daher bräuchte man eine einfache Möglichkeit, Code mit dimensionsabhängigen Funktions- und Typnamen zu generieren. Außerdem ist der Code in ANSI C geschrieben, sodass das abgefahrene C++-Zeug mit Vorlagen und Spezialisierungen hier nicht anwendbar ist.

    – JJ.

    29. September 2009 um 3:33 Uhr

  • Stimmen Sie für die Wiedereröffnung ab, da diese Frage spezifisch für die rekursive Makroerweiterung ist und stackoverflow.com/questions/216875/using-in-macros ein allgemeines “Wofür ist es gut” ist. Der Titel dieser Frage sollte präzisiert werden.

    – Ciro Santilli OurBigBook.com

    31. Mai 2015 um 23:26 Uhr


  • Ich wünschte, dieses Beispiel wäre minimiert worden: Das gleiche passiert auf #define A 0 \n #define M a ## A: zwei haben ## ist nicht der Schlüssel.

    – Ciro Santilli OurBigBook.com

    21. Juni 2015 um 9:49 Uhr


Benutzeravatar von Jonathan Leffler
Jonathan Leffler

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 Ebenen der Indirektion

In einem Kommentar zu einer anderen Antwort fragte Cade Roux, warum dies zwei Ebenen der Indirektion erfordert. Die leichtfertige Antwort ist, weil der Standard es so verlangt; Sie werden feststellen, dass Sie auch mit dem Zeichenfolgenoperator den entsprechenden Trick 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, findet eine Argumentsubstitution statt. Ein Parameter in der Ersetzungsliste, sofern ihm nicht ein vorangestellt ist # oder ## Vorverarbeitungstoken oder gefolgt von a ## Vorverarbeitungstoken (siehe unten), wird durch das entsprechende Argument ersetzt, nachdem alle darin enthaltenen Makros expandiert 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 anderen Vorverarbeitungstoken verfügbar.

Im Aufruf NAME(mine), das Argument ist ‘mein’; es ist vollständig auf „meins“ erweitert; es wird dann in die Ersetzungszeichenfolge eingesetzt:

EVALUATOR(mine, VARIABLE)

Jetzt wird das Makro EVALUATOR entdeckt und die Argumente werden als „mein“ und „VARIABLE“ 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 unmittelbar vor oder nach einem Parameter ein a steht ## Vorverarbeitungstoken, der Parameter wird durch die Vorverarbeitungstokensequenz des entsprechenden Arguments ersetzt; […]

Sowohl für objektartige als auch funktionsartige Makroaufrufe wird jede Instanz von a ## Vorverarbeitungstoken in der Ersetzungsliste (nicht aus einem Argument) wird gelöscht und das vorhergehende Vorverarbeitungstoken wird mit dem folgenden Vorverarbeitungstoken verkettet.

So enthält die Ersatzliste x gefolgt von ## und auch ## gefolgt von y; also haben wir:

mine ## _ ## 3

und Beseitigung der ## Tokens und das Verketten der Tokens auf beiden Seiten kombiniert ‘mine’ mit ‘_’ und ‘3’, um Folgendes zu ergeben:

mine_3

Dies ist das gewünschte Ergebnis.


Wenn wir uns die ursprüngliche Frage ansehen, lautete der Code (angepasst, um „mine“ anstelle von „some_function“ zu verwenden):

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

NAME(mine)

Das Argument für NAME ist eindeutig „meins“, und das ist vollständig erweitert.
Nach den Regeln von 6.10.3.3 finden wir:

mine ## _ ## VARIABLE

was, wenn die ## Operatoren werden eliminiert, werden abgebildet auf:

mine_VARIABLE

genau wie in der Frage angegeben.


Traditioneller C-Präprozessor

Robert Rüger fragt:

Gibt es eine Möglichkeit, dies mit dem traditionellen C-Präprozessor zu tun, der keinen Token-Einfügeoperator hat ##?

Vielleicht, vielleicht auch nicht – das hängt vom Präprozessor ab. Einer der Vorteile des Standard-Präprozessors ist, dass er über diese Funktion verfügt, die zuverlässig funktioniert, 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, kein Leerzeichen erzeugt, wie es der ANSI-Präprozessor tun muss. Der GCC (6.3.0) C-Präprozessor erfüllt diese Anforderung; der Clang-Präprozessor von XCode 8.2.1 nicht.

Wenn es funktioniert, macht 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 kein Leerzeichen dazwischen steht fun, und VARIABLE – das ist wichtig, denn wenn vorhanden, wird es in die Ausgabe kopiert, und Sie erhalten am Ende mine_ 3 wie der Name, der natürlich nicht syntaktisch gültig ist. (Kann ich jetzt bitte meine Haare zurück haben?)

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

# 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 erhalte 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 lästigen und unzuverlässigen Prozess beim Versuch, Code zu portieren, machte. Der Standard mit der ## Notation vereinfacht das radikal.

Es könnte andere Möglichkeiten geben, 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);

Schließen, aber keine Würfel. YMMV natürlich, abhängig von dem Pre-Standard-Präprozessor, den Sie verwenden. Ehrlich gesagt, wenn Sie mit einem Präprozessor festsitzen, der nicht kooperiert, wäre es wahrscheinlich einfacher, einen Standard-C-Präprozessor anstelle des Prä-Standard-Präprozessors zu verwenden (normalerweise gibt es eine Möglichkeit, den Compiler entsprechend zu konfigurieren). verbringen viel Zeit damit, einen Weg zu finden, die Arbeit zu erledigen.

  • Ja, das löst das Problem. Ich kannte den Trick mit zwei Rekursionsebenen – ich musste mindestens einmal mit Stringifizierung spielen – aber ich wusste nicht, wie man das macht.

    – JJ.

    29. September 2009 um 3:34 Uhr

  • Gibt es eine Möglichkeit, dies mit dem zu tun traditioneller C-Präprozessor der den Token-Einfügeoperator ## nicht hat?

    – Robert Rüger

    24. Dezember 2016 um 0:03 Uhr

  • @RobertRüger: Es verdoppelt die Länge der Antwort, aber ich habe Informationen hinzugefügt, die behandelt werden sollen cpp -traditional. Beachten Sie, dass es keine endgültige Antwort gibt – dies hängt von Ihrem Präprozessor ab.

    – Jonathan Leffler

    24. Dezember 2016 um 2:40 Uhr

  • Vielen Dank für die Antwort. Das ist total toll! Inzwischen 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

    26. Dezember 2016 um 11:03 Uhr

Benutzeravatar von Stephen Canon
Stefan Kanon

Verwenden:

#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 wollen Sie nicht wissen, warum das funktioniert. Wenn Sie wissen, warum es funktioniert, werden Sie es werden dieser Kerl bei der Arbeit, der so etwas weiß, und jeder wird kommen und dir Fragen stellen. =)

  • Können Sie erklären, warum es zwei Ebenen der Indirektion braucht. 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 funktionierte.

    – Cade Roux

    29. September 2009 um 1:31 Uhr

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

Leicht verständliche Erklärung der EVALUATOR zweistufiges Muster

Ich habe nicht jedes Wort des C-Standards vollständig verstanden, aber ich denke, dies ist ein vernünftiges Arbeitsmodell dafür, wie die in Jonathan Lefflers Antwort gezeigte Lösung funktioniert, etwas ausführlicher erklärt. Lassen Sie mich wissen, wenn mein Verständnis falsch ist, hoffentlich mit einem Minimalbeispiel, das meine Theorie widerlegt.

Für unsere Zwecke können wir uns die Makroerweiterung in drei Schritten vorstellen:

  1. (Prescan) Makroargumente werden ersetzt:
    • Wenn sie Teil einer Verkettung oder Stringifizierung sind, werden sie genau so ersetzt wie die beim Makroaufruf angegebene Zeichenfolge, ohne dass sie erweitert werden
    • Andernfalls werden sie zuerst vollständig ausgebaut und erst dann ersetzt
  2. Stringifikation und Verkettung passieren
  3. Alle definierten Makros werden expandiert

Schritt-für-Schritt-Beispiel ohne Umleitung

Haupt c

#define CAT(x) pref_ ## x
#define Y a

CAT(Y)

und erweitere es mit:

gcc -E main.c

wir bekommen:

pref_Y

Weil:

Schritt 1: Y ist ein Makroargument von CAT.

x erscheint in einer Stringifizierung pref_ ## x. Deswegen, Y wird so eingefügt, wie es ist, ohne dass die Erweiterung Folgendes gibt:

pref_ ## Y

Schritt 2: Die Verkettung erfolgt und wir haben übrig:

pref_Y

Schritt 3: Jede weitere Makroersetzung findet statt. Aber pref_Y ist kein bekanntes Makro, also wird es in Ruhe gelassen.

Wir können diese Theorie bestätigen, indem wir tatsächlich eine Definition hinzufügen pref_Y:

#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf

CAT(Y)

und jetzt wäre das Ergebnis:

asdf

weil auf Schritt 3 oben pref_Y ist jetzt als Makro definiert und wird daher erweitert.

Schritt-für-Schritt-Beispiel mit Indirektion

Wenn wir jedoch das Zwei-Schritt-Muster verwenden:

#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a

CAT(Y)

wir bekommen:

pref_a

Schritt 1: CAT ausgewertet wird.

CAT(x) ist definiert als CAT2(x)also Argument x von CAT bei der Definition taucht bei einer Stringifizierung nicht auf: die Stringifizierung findet erst danach statt CAT2 erweitert wird, was in diesem Schritt nicht zu sehen ist.

Deswegen, Y vollständig erweitert wird, bevor es ersetzt wird, wobei die Schritte 1, 2 und 3 durchlaufen werden, die wir hier weglassen, weil es trivial erweitert wird a. Also setzen wir a in CAT2(x) geben:

CAT2(a)

Schritt 2: Es muss keine Stringifizierung durchgeführt werden

Schritt 3: Erweitern Sie alle vorhandenen Makros. Wir haben das Makro CAT2(a) und so fahren wir fort, das zu erweitern.

Schritt 3.1: das Argument x von CAT2 erscheint in einer Stringifizierung pref_ ## x. Fügen Sie daher die Eingabezeichenfolge ein a wie es ist, geben:

pref_ ## a

Schritt 3.2: stringifizieren:

pref_a

Schritt 3: Erweitern Sie alle weiteren Makros. pref_a ist kein Makro, also sind wir fertig.

GCC-Argument Prescan-Dokumentation

Lesenswert ist auch die Dokumentation von GCC zu diesem Thema: https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

Bonus: Wie diese Regeln verhindern, dass verschachtelte Aufrufe unendlich werden

Überlegen Sie nun:

#define f(x) (x + 1)

f(f(a))

was sich erweitert zu:

((a + 1) + 1)

anstatt unendlich zu werden.

Lassen Sie es uns aufschlüsseln:

Schritt 1: das Äußere f wird mit Argument aufgerufen x = f(a).

In der Definition von fdas Argument x ist nicht Teil einer Verkettung in der Definition (x + 1) von f. Deshalb wird es erst komplett ausgebaut, bevor es ausgetauscht wird.

Schritt 1.1.: Wir erweitern das Argument vollständig x = f(1) gemäß den Schritten 1, 2 und 3 geben x = (a + 1).

Jetzt zurück in Schritt 1 nehmen wir das vollständig erweitert x Argument gleich (a + 1)und fügen Sie es in die Definition von ein f Geben:

((a + 1) + 1)

Schritte 2 und 3: Es passiert nicht viel, da wir keine Stringifizierung und keine weiteren Makros zum Erweitern haben.

1425090cookie-checkWie kann ich zweimal mit dem C-Präprozessor verketten und ein Makro wie in “arg ## _ ## MACRO” erweitern?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy