Tool zum Verfolgen der Ausführung des C-Präprozessors während der Makroerweiterung?

Lesezeit: 6 Minuten

Benutzer-Avatar
Chadjoan

Gibt es eine Möglichkeit, Schritt für Schritt zu drucken, was der C-Präprozessor tut, wenn er ein Makro erweitert?

Zum Beispiel würde ich ihm Text in C-Sprache (z. B. .h-Datei(en)) zur Vorverarbeitung geben. Zur Veranschaulichung hier ein einfaches Beispiel:

// somefile.h
#define q r
#define bar(x,z) x ## z
#define baz(y) qux ## y
#define foo(x,y) bar(x, baz(y))

Bisher geht es nur darum, eine Definitionstabelle zu erstellen.

Als nächstes kommt der Text, der im Detail erweitert werden soll. Für diese Demonstration erwarte ich, dass der Workflow/Prozess/die Ausgabe in etwa so aussieht:

$ magical_cpp_revealer  somefile.h

Please enter some preprocessor text to analyse:
> foo(baz(p),q)

Here are the resulting preprocessor calculations:
,----.----.---------------------------.-----------------------------------------
|Step|Exp#|  Expression               |  Reason
|====|====|===========================|=========================================
| 00 | 00 |  foo(baz(p),q)            |  Original tokens.
| 01 |    |                           |  Definition found for 'foo': `foo(x,y)` = "bar(x, baz(y))"
| 02 | 01 |  bar(x, baz(y))           |  'foo' begins expansion. Original tokens shown.
| 03 |    |                           |  'foo' Stage 1: Raw parameter replacements elided: no # or ## operators present.
| 04 |    |                           |  'foo' Stage 2: Stringification elided: no # operators present.
| 05 |    |                           |  'foo' Stage 3: Concatenation elided: no ## operators present.
| 06 |    |                           |  'foo' Stage 4: Argument scan begins.
| 07 |    |                           |    Argument for parameter 'x' is "baz(p)"
| 08 | 02 |    baz(p)                 |    Scanning "baz(p)" for macros to expand.
| 09 |    |                           |    Definition found for 'baz': `baz(y)` = "qux ## y"
| 10 | 03 |    qux ## y               |    'baz' begins expansion. Original tokens shown.
| 11 | 04 |    qux ## p               |      'foo->baz' Stage 1: Raw parameter replacements performed
| 12 |    |                           |         using 'y' = "p".
| 13 |    |                           |      'foo->baz' Stage 2: Stringification elided: no # operators present.
| 14 | 05 |    quxp                   |      'foo->baz' Stage 3: Concatenation performed.
| 15 |    |                           |      'foo->baz' Stage 4: Argument scan elided: no parameters present.
| 16 |    |                           |      'foo->baz' Stage 5: Expansive parameter replacements elided: no parameters present.
| 17 |    |                           |      'foo->baz' Stage 6: Rescan begins
| 18 |    |                           |        No definition for 'quxp'
| 19 |    |                           |      'foo->baz' Stage 6: Rescan concludes.
| 20 | 06 |    quxp                   |    'baz' concludes expansion. Final result shown.
| 21 |    |                           |  'foo' Stage 4: Argument scan continues.
| 22 |    |                           |    Currently:
| 23 |    |                           |      'x' = "quxp"
| 24 |    |                           |      'y' = To Be Determined
| 25 |    |                           |    Argument for parameter 'y' is "q"
| 26 | 07 |    q                      |    Scanning "q" for macros to expand.
| 27 |    |                           |    Definition found for 'q': `q` = "r"
| 28 | 08 |    r                      |    'q' begins expansion. Original tokens shown.
| 29 |    |                           |      'foo->q': Stage 1: Concatenation elided: no ## operators present.
| 30 |    |                           |      'foo->q': Stage 2: Scan begins.
| 31 |    |                           |        No definition for 'r'
| 32 |    |                           |      'foo->q': Stage 2: Scan concludes.
| 33 | 09 |    r                      |    'q' concludes expansion. Final result shown.
| 34 |    |                           |  'foo' Stage 4: Argument scan concludes.
| 35 | 10 |  bar(x, baz(y))           |  'foo': Reminder of current token sequence.
| 36 | 11 |  bar(quxp, baz(r))        |  'foo' Stage 5: Expansive parameter replacements performed
| 37 |    |                           |     using 'x' = "quxp",
| 38 |    |                           |       and 'y' = "r".
| 39 |    |                           |  'foo' Stage 6: Rescan begins
| 40 |    |                           |    Definition found for 'bar': `bar(x,z)` = "x ## z"
| 41 | 12 |    x ## z                 |    'bar' begins expansion. Original tokens shown.
| 42 | 13 |    quxp ## baz(r)         |      'foo->bar' Stage 1: Raw parameter replacements performed
| 43 |    |                           |         using 'x' = "quxp",
| 44 |    |                           |           and 'z' = "baz(r)".
| 45 |    |                           |      'foo->bar' Stage 2: Stringification elided: no # operators present.
| 46 | 14 |    quxpbaz(r)             |      'foo->bar' Stage 3: Concatenation performed.
| 47 |    |                           |      'foo->bar' Stage 4: Argument scan elided: no parameters present.
| 48 |    |                           |      'foo->bar' Stage 5: Expansive parameter replacements elided: no parameters present.
| 49 |    |                           |      'foo->bar' Stage 6: Rescan begins
| 50 |    |                           |        No definition for 'quxpbaz'
| 51 |    |                           |        No definition for '('
| 52 |    |                           |        No definition for 'r'
| 53 |    |                           |        No definition for ')'
| 54 |    |                           |      'foo->baz' Stage 6: Rescan concludes.
| 55 | 15 |    quxpbaz(r)             |    'bar' concludes expansion. Final result shown.
| 56 |    |                           |  'foo' Stage 6: Rescan concludes
| 57 | 16 |  quxpbaz(r)               |  'foo' concludes expansion. Final result shown.
'----'----'---------------------------'-----------------------------------------

(Nebenbemerkung und Vorbehalt für zukünftige Leser: Ich habe den obigen Trace von Hand geschrieben und er ist möglicherweise nicht 100% korrekt, zumindest in Bezug auf die Darstellung der Funktionsweise des Präprozessors.)

Beachten Sie, dass ich versucht habe, nicht nur die positiven Entscheidungen des Präprozessors darüber, was was ist, zu veranschaulichen machen (z. B. wenn es eine Definition gefunden hat und anfängt zu expandieren), sondern auch seine negativen Entscheidungen über was veranschaulicht nicht zu tun (z. B. wenn ein Token keine Definition hat oder wenn #+##-Operatoren nicht vorhanden sind). Das mag etwas spezifisch klingen, aber es ist wichtig zu verstehen, warum der Präprozessor etwas nicht getan hat, was ich von ihm erwartet hatte, oft mit einer banalen Schlussfolgerung wie „Ich habe die Definition oder das Token falsch geschrieben“ oder „Ich habe vergessen, # füge diese eine Datei hinzu”.

Ich wäre noch erleichterter, wenn es eine Möglichkeit gibt, zu enthüllen, was MSVC ist CL.EXE denkt, wenn es “traditionelle Präprozessorlogik” verwendet, um meine Makros zu erweitern.

Hier ist ein Beispiel dafür, was nicht beantworte die Frage:

$ gcc -E somefile.h
...
quxpbaz(r)

Das finde ich in den Antworten auf Fragen wie Any Utility to test expand C/C++ #define macros?.

Wenn jemand nach der “Erweiterung” eines Makros fragt, gcc -E scheint eine gültige Antwort zu sein. Ich suche etwas mit höherer Wiedergabetreue, und ich kenne es bereits gcc -E.

Ich schreibe ISO C11-Code, schließe aber den ein C++ Tag, falls es in diesem Ökosystem ein Tool oder eine Technik gibt, die dafür relevant ist.

Ich hoffe, dass jemand da draußen, der dies liest, vielleicht ein Compiler-Autor ist, der ähnliche Arbeiten durchgeführt oder gesehen hat (Compiler-Tracing-Optionen?) oder ein Tool wie dieses verfasst hat oder einfach viel mehr Glück mit seinen Suchergebnissen hat als ich. Oder wenn Sie alle C-Sprachangebote im Auge behalten und relativ sicher sind, dass dies nicht existiert, dann würde ich auch eine negative Antwort hilfreich finden, obwohl ich neugierig wäre, warum der C-Präprozessor wäre seit Jahrzehnten auf dem Markt gewesen, wegen seiner “Fallstricke” berüchtigt gewesen und hätte dennoch nie ein Werkzeug (oder einen Prozess) gesehen, um den Vorhang vor dem Präprozessor zurückzuziehen. (Ich hoffe, das gibt es tatsächlich. Daumen drücken)

  • Da Fragen zur Empfehlung eines Tools nicht zum Thema gehören, habe ich den ersten Absatz etwas geändert 😀

    – Antti Haapala – Слава Україні

    28. Oktober 2020 um 9:24 Uhr

  • Ich bekomme hier wirklich einen “Lieber Weihnachtsmann …” Eindruck. Aber es ist klar, recherchiert und ich will es selbst so sehr. Haben Sie also eine Upvote statt einer Close-Vote. 😉

    – Yunnosch

    28. Oktober 2020 um 10:07 Uhr

  • Während ein solches Feature sicherlich für die Autoren eines Präprozessors nützlich sein wird, halte ich es nicht für eine gute Sache für fast alle Benutzer. Wenn Ihre Präprozessormagie so kompliziert ist, dass Sie diese Analyse sehen müssen, sollten Sie Ihr Design überdenken.

    – die fleißige Biene

    28. Oktober 2020 um 11:18 Uhr

  • Sie müssen gcc nicht disassemblieren, um einen Präprozessor zu finden. Diese sind möglicherweise einfacher zu erweitern, um das zu tun, was Sie möchten. github.com/boostorg/wave github.com/lpsantil/ucpp github.com/facebookresearch/CParser

    – Jerry Jeremia

    7. Dezember 2020 um 21:00 Uhr

  • Ich bin mir nicht sicher, ob Sie danach suchen, aber IDEs wie Eclipse können eine schrittweise Erweiterung durchführen. Sicherlich nicht so ausführlich wie Sie beschreiben, aber dennoch praktisch genug. stackoverflow.com/questions/35472290/…

    – Eugen Sch.

    19. Februar 2021 um 20:53 Uhr

Benutzer-Avatar
Morten Jensen

Ich würde vorschlagen, einen hochwertigen Compiler/Präprozessor zu finden und den Präprozessor zu bearbeiten.

Ich würde GCC und Clang vermeiden, da sie meiner Meinung nach zu schwer sind. Ich würde mir cparser von libfirm und insbesondere diese Datei ansehen: https://github.com/libfirm/cparser/blob/master/src/parser/preprocessor.c

Code von libfirm ist super einfach zu lesen und zu bearbeiten, und es dauert fast keine Zeit, das Projekt zu erstellen – im groben Gegensatz zu LLVM/clang oder GCC.

Es hat den gesamten C99-Code gefressen, den ich bisher darauf geworfen habe.

Übrigens bin ich kein Affiliate, ich finde es einfach cool! Ich habe den Code gerade mit großartigen Ergebnissen verwendet und fantastische Unterstützung, Hilfe und Anleitung auf dem IRC-Kanal #firm @ freenode erhalten.

BEARBEITEN:

Sparse, wie es vom Kernel-Hausmeister-Team in Linux verwendet wird, lässt sich für solche Zwecke ebenfalls leicht hacken. Es enthält auch einen c-Präprozessor: https://github.com/chrisforbes/sparse

https://www.kernel.org/doc/html/v4.12/dev-tools/sparse.html

1095010cookie-checkTool zum Verfolgen der Ausführung des C-Präprozessors während der Makroerweiterung?

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

Privacy policy