Wie geht der C-Präprozessor mit zirkulären Abhängigkeiten um?

Lesezeit: 7 Minuten

Benutzeravatar von deepak
deepak

Ich möchte wissen, wie die C Der Präprozessor behandelt zirkuläre Abhängigkeiten (von #defines). Das ist mein Programm:

#define ONE TWO 
#define TWO THREE
#define THREE ONE

int main()
{
    int ONE, TWO, THREE;
    ONE = 1;
    TWO = 2;
    THREE = 3;
    printf ("ONE, TWO, THREE = %d,  %d, %d \n",ONE,  TWO, THREE);
}

Hier ist die Ausgabe des Präprozessors. Ich kann nicht herausfinden, warum die Ausgabe so ist. Ich würde gerne wissen, welche verschiedenen Schritte ein Präprozessor in diesem Fall unternimmt, um die folgende Ausgabe zu erhalten.

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

int main()
{
 int ONE, TWO, THREE;
 ONE = 1;
 TWO = 2;
 THREE = 3;
 printf ("ONE, TWO, THREE = %d,  %d, %d \n",ONE, TWO, THREE);
}

Ich führe dieses Programm unter Linux 3.2.0-49-generic-pae aus und kompiliere in gcc Version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5).

Benutzeravatar von rici
rici

Während ein Präprozessormakro erweitert wird, wird der Name dieses Makros nicht erweitert. Alle drei Ihrer Symbole sind also als sie selbst definiert:

ONE -> TWO -> THREE -> ONE (not expanded because expansion of ONE is in progress)
TWO -> THREE -> ONE -> TWO (        "                         TWO      "        )
THREE -> ONE -> TWO -> THREE (      "                         THREE    "        )

Dieses Verhalten wird durch §6.10.3.4 des C-Standards festgelegt (Abschnittsnummer aus dem C11-Entwurf, obwohl meines Wissens der Wortlaut und die Nummerierung des Abschnitts seit C89 unverändert sind). Wenn ein Makroname gefunden wird, wird er durch seine Definition ersetzt (und # und ## Präprozessor-Operatoren behandelt werden, sowie Parameter zu funktionsähnlichen Makros). Dann wird das Ergebnis erneut nach weiteren Makros durchsucht (im Kontext des Rests der Datei):

2/ Wenn der Name des zu ersetzenden Makros während dieses Scannens der Ersetzungsliste gefunden wird (ohne den Rest der Vorverarbeitungstoken der Quelldatei), wird er nicht ersetzt. Wenn außerdem verschachtelte Ersetzungen auf den Namen des zu ersetzenden Makros stoßen, wird es nicht ersetzt …

Die Klausel besagt weiter, dass jedes Token, das aufgrund eines rekursiven Aufrufs nicht ersetzt wird, effektiv “eingefroren” wird: Es wird niemals ersetzt:

… Diese nicht ersetzten Makronamen-Vorverarbeitungstoken stehen nicht mehr für einen weiteren Ersatz zur Verfügung, selbst wenn sie später in Kontexten (erneut) untersucht werden, in denen dieses Makronamen-Vorverarbeitungstoken ansonsten ersetzt worden wäre.

Die Situation, auf die sich der letzte Satz bezieht, kommt in der Praxis selten vor, aber hier ist der einfachste Fall, der mir einfällt:

#define two one,two
#define a(x) b(x)
#define b(x,y) x,y
a(two)

Das Ergebnis ist one, two. two erweitert wird one,two beim Austausch von aund das erweiterte two wird als vollständig erweitert markiert. Anschließend, b(one,two) erweitert wird. Dies ist nicht mehr im Rahmen der Ablösung von twoaber die two das ist das zweite Argument von b wurde eingefroren, wird also nicht wieder erweitert.

  • +1, ausgezeichnete Antwort. Hier ist ein Beispiel, das dieses Verhalten meiner Meinung nach gut demonstriert (aber leider etwas zu lang für einen Kommentar).

    – Ilmari Karonen

    13. Juni 2014 um 11:46 Uhr

  • @IlmariKaronen: Ich habe ein Beispiel für den letzten Satz von Absatz 2 hinzugefügt, der sonst etwas schwierig zu verstehen ist. Aber wenn Sie Ihren Kommentar / Ihre Antwort noch einmal lesen, glaube ich nicht, dass Sie darauf abzielten. Sie müssen also nicht sagen, dass Ihr Beispiel ungefähr mit dem OP übereinstimmt, obwohl das Ergebnis möglicherweise etwas visueller ist.

    – Rici

    13. Juni 2014 um 21:36 Uhr

Ihre Frage wird durch die Veröffentlichung beantwortet ISO/IEC 9899:TC2 Abschnitt 6.10.3.4 „Erneutes Scannen und weiteres Ersetzen“, Absatz 2, den ich hier der Einfachheit halber zitiere; in der Zukunft, Bitte lesen Sie die Spezifikation, wenn Sie eine Frage zur Spezifikation haben.

Wenn der Name des zu ersetzenden Makros während dieses Scans der Ersetzungsliste gefunden wird (ohne die restlichen Vorverarbeitungstoken der Quelldatei), wird er nicht ersetzt. Wenn darüber hinaus verschachtelte Ersetzungen auf den Namen des zu ersetzenden Makros stoßen, wird es nicht ersetzt. Diese nicht ersetzten Makronamen-Vorverarbeitungstoken stehen nicht mehr für einen weiteren Ersatz zur Verfügung, selbst wenn sie später in Kontexten (erneut) untersucht werden, in denen dieses Makronamen-Vorverarbeitungstoken ansonsten ersetzt worden wäre.

  • Um fair zu sein, ist das Finden und Verstehen der Antwort im C-Standard keine triviale Aufgabe. Mit Ihrer „Lesen Sie den Standard“-Logik könnten wir jede einzelne Frage zu C mit RTFM beantworten.

    – Ludin

    12. Juni 2014 um 6:54 Uhr

  • @Lundin: Die Spezifikation beginnt mit einem Inhaltsverzeichnis, das eindeutig angibt, in welchem ​​Abschnitt der Spezifikation die Makroerweiterung behandelt wird. Ich habe ganze 30 Sekunden gebraucht, um den richtigen Absatz zu finden, und ich bin kein Experte für die C-Spezifikation. Und ja, mit meinem ausgezeichneten Vorschlag, dass Leute tatsächlich den Standard lesen, wenn sie eine Frage zu einer standardisierten Sprache haben, würden die meisten der schlechten Fragen in diesem Tag verschwinden. Das ist gut.

    – Eric Lippert

    12. Juni 2014 um 7:02 Uhr


  • Außer das ist keine schlechte Frage. Das OP hat einige Nachforschungen angestellt, ein Beispiel zum Kompilieren und die Ausgabe des Vorprozessors aufgenommen, den Compiler und das System usw. angegeben. Und es scheint keine offensichtlichen Duplikate der Frage zu geben. Auch hier ist das Lesen der C-Norm keine triviale Aufgabe. Das ist Ihnen zum Beispiel nicht gelungen. Sie zitieren aus irgendeinem Grund einen Entwurf N1124 zu ISO 9899:1999 TC2, der seitdem durch C99+TC2, C99+TC3 Entwurf N1256, C99+TC3, C11, C11+TC1 ersetzt wurde. Obwohl ich sicher bin, dass Sie alle Änderungen des erneuten Scannens von Makros durch diese Revisionen kennen …

    – Ludin

    12. Juni 2014 um 11:46 Uhr


  • @Lundin: Was schlechte Fragen betrifft, sehe ich hier jeden Tag 100-mal schlechtere Fragen, also ja, es ist ziemlich gut. Ich habe diese Version des Standards gewählt, weil sie leicht zu finden ist – sie ist von Wikipedia verlinkt – und sie ist kostenlos, und die meisten Compiler entsprechen ihr. Wie gesagt, ich bin überhaupt kein Experte für die Geschichte oder den Inhalt der C-Spezifikation; Mein Punkt ist, dass ich es geschafft habe, mit einer Websuche und einem Blick auf das Inhaltsverzeichnis eine Antwort auf die Frage zu finden. Dies ist für den durchschnittlichen Programmierer nicht außerhalb des Bereichs der Möglichkeiten. Ich ermutige das Lesen von Spezifikationen in der Toolbox aller Programmierer.

    – Eric Lippert

    12. Juni 2014 um 13:47 Uhr

  • @Alice: Obwohl cHao sich vielleicht eleganter hätte ausdrücken können, ist der Punkt gut getroffen. Die Korrektheitsbeweise von hundertzeiligen Methoden in Bibliothekscode, der eine klare Spezifikation hat, interessieren mich grundsätzlich nicht; Ich mache mir Sorgen um die Korrektheit ganzer Betriebssysteme, ganzer Datenbanken usw. in einer Welt mit schwachen Speichermodellen, speicherunsicheren Sprachen usw. Die Techniken, die Sie verwenden, um die Korrektheit der STL-Auflistungen zu beweisen, lassen sich nicht auf das gesamte Windows-Betriebssystem skalieren.

    – Eric Lippert

    25. Juni 2014 um 22:20 Uhr


https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html#Self-Referential-Macros beantwortet die Frage nach selbstreferenziellen Makros.

Der Kern der Antwort ist, dass der Präprozessor, wenn er auf sich selbst verweisende Makros findet, diese überhaupt nicht erweitert.

Ich vermute, dass die gleiche Logik verwendet wird, um die Erweiterung von zirkulär definierten Makros zu verhindern. Andernfalls befindet sich der Präprozessor in einer unendlichen Erweiterung.

In Ihrem Beispiel führen Sie die Makroverarbeitung durch, bevor Sie gleichnamige Variablen definieren, sodass Sie unabhängig vom Ergebnis der Makroverarbeitung immer drucken 1, 2, 3!

Hier ist ein Beispiel, wo die Variablen zuerst definiert werden:

#include <stdio.h>
int main()
{
    int A = 1, B = 2, C = 3;
#define A B
#define B C
//#define C A
    printf("%d\n", A);
    printf("%d\n", B);
    printf("%d\n", C);
}

Das druckt 3 3 3. Etwas hinterhältig, unkommentiert #define C A ändert das Verhalten der Leitung printf("%d\n", B);

Hier ist eine schöne Demonstration des in den Antworten von rici und Eric Lippert beschriebenen Verhaltens, dh dass ein Makroname nicht erneut erweitert wird, wenn er erneut angetroffen wird, während dasselbe Makro bereits erweitert wird.

Inhalt von test.c:

#define ONE 1, TWO
#define TWO 2, THREE
#define THREE 3, ONE

int foo[] = {
  ONE,
  TWO,
  THREE
};

Ausgabe von gcc -E test.c (ohne anfängliche # 1 ... Linien):

int foo[] = {
  1, 2, 3, ONE,
  2, 3, 1, TWO,
  3, 1, 2, THREE
};

(Ich würde dies als Kommentar posten, aber das Einfügen umfangreicher Codeblöcke in Kommentare ist etwas umständlich, daher mache ich dies stattdessen zu einer Community-Wiki-Antwort. Wenn Sie der Meinung sind, dass es besser als Teil einer vorhandenen Antwort enthalten wäre, zögern Sie nicht um es zu kopieren und mich zu bitten, diese CW-Version zu löschen.)

1414860cookie-checkWie geht der C-Präprozessor mit zirkulären Abhängigkeiten um?

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

Privacy policy