2010-07-09 13 views
9

Dies ist eine eher theoretische Frage zu Makros (glaube ich). Ich weiß, dass Makros Quellcode verwenden und Objektcode erzeugen, ohne ihn zu evaluieren, was es Programmierern ermöglicht, vielseitige syntaktische Strukturen zu erstellen. Wenn ich diese beiden Makrosysteme klassifizieren müsste, würde ich sagen, dass es das Makro "C style" und das Makro "Lisp style" gibt.Wie verfolgt eine Makro-fähige Sprache den Quellcode für das Debugging?

Es scheint, dass das Debugging von Makros ein bisschen schwierig sein kann, da der aktuell laufende Code zur Laufzeit von der Quelle abweicht.

Wie verfolgt der Debugger die Ausführung des Programms in Bezug auf den vorverarbeiteten Quellcode? Gibt es einen speziellen "Debug-Modus", der gesetzt werden muss, um zusätzliche Daten über das Makro zu erfassen?

In C kann ich verstehen, dass Sie eine Kompilierzeit für das Debuggen festlegen würden, aber wie würde eine interpretierte Sprache, wie einige Formen von Lisp, es tun?

Entschuldigen Sie sich dafür, dass Sie das nicht ausprobiert haben, aber die lisp toolchain benötigt mehr Zeit als ich ausgeben muss, um herauszufinden.

Antwort

3

Ich glaube nicht, dass es einen fundamentalen Unterschied zwischen den Makros "C style" und "Lisp style" gibt. Beide transformieren die Quelle, bevor der eigentliche Compiler sie sieht. Der große Unterschied besteht darin, dass die Makros von C den C-Präprozessor verwenden (eine schwächere Sekundärsprache, die hauptsächlich für die einfache String-Substitution verwendet wird), während die Lisp-Makros in Lisp selbst geschrieben sind (und somit überhaupt etwas tun können).

(Nebenbei bemerkt: Ich habe seit einiger Zeit kein nicht kompiliertes Lisp gesehen ... sicherlich nicht seit der Jahrhundertwende. Aber wenn überhaupt, würde die Interpretation das Problem des Makro-Debuggens leichter machen, nicht schwieriger, da du mehr Informationen hast.)

Ich stimme Michael zu: Ich habe keinen Debugger für C gesehen, der überhaupt mit Makros arbeitet. Code, der Makros verwendet, wird transformiert, bevor etwas passiert. Die "debug" mode for compiling C code bedeutet im Allgemeinen nur speichert functions, types, variables, filenames, and such - ich glaube nicht, dass einer von ihnen Informationen über Makros speichern.

  • Für Debug-Programme, die Makros ist Lisp so ziemlich das gleiche wie C hier verwenden : Ihr Debugger den kompilierten Code sieht, nicht das Makro Anwendung. In der Regel werden Makros einfach gehalten und unabhängig voneinander vor der Verwendung debuggt, um die Notwendigkeit dafür zu vermeiden, genau wie C.

  • für das Debuggen des Makros selbst, bevor Sie sie irgendwo hingehen und verwenden, tut Lisp haben Eigenschaften , die dies machen einfacher als in C, zB der repl und macroexpand-1 (obwohl in C gibt es offensichtlich ein Weg zu Makroexpand eine ganze Datei, vollständig, um einmal). Sie können die vor und nach einer Makroexpansion, direkt in Ihrem Editor sehen, wenn Sie es schreiben.

Ich kann keine Zeit erinnere ich mich auf eine Situation lief, wo in eine Makrodefinition Debuggen selbst nützlich gewesen wäre. Entweder ist es ein Fehler in der Makrodefinition, in diesem Fall macroexpand-1 isoliert das Problem sofort, oder es ist ein Fehler unter diesem, in diesem Fall die normalen Debugging-Einrichtungen funktionieren gut und es ist mir egal, dass eine Makroexpansion zwischen zwei Frames meines Aufrufs aufgetreten ist Stapel.

1

Ich weiß nichts über Lisp-Makros (die ich vermute, sind wahrscheinlich ganz anders als C-Makros) oder Debugging, aber viele - wahrscheinlich die meisten - C/C++ - Debugger behandeln Source-Level-Debugging von C-Präprozessor-Makros nicht besonders gut .

Im Allgemeinen "C" C/C++ - Debugger sie nicht in die Makrodefinition "Schritt". Wenn ein Makro in mehrere Anweisungen erweitert wird, bleibt der Debugger normalerweise immer in der gleichen Quellzeile (wo das Makro aufgerufen wird) für jeden Debuggerschritt.

Dies kann Debugging-Makros etwas schmerzhafter machen, als sie es sonst sein könnten - ein weiterer Grund, sie in C/C++ zu vermeiden. Wenn sich ein Makro auf wirklich mysteriöse Weise falsch verhält, werde ich in den Assembly-Modus gehen, um es zu debuggen oder das Makro zu erweitern (entweder manuell oder mit dem Schalter des Compilers). Es ist ziemlich selten, dass man so extrem gehen muss; Wenn Sie Makros schreiben, die so kompliziert sind, gehen Sie wahrscheinlich den falschen Weg.

1

Normalerweise wird beim C-Quell-Level-Debugging die Zeilengranularität ("next" -Befehl) oder die Granularität auf Befehlsebene ("step in") verwendet. Makroprozessoren fügen spezielle Direktiven in die verarbeitete Quelle ein, die es dem Compiler ermöglichen, kompilierte Sequenzen von CPU-Anweisungen auf Quellcodezeilen abzubilden.

In Lisp gibt es keine Konvention zwischen Makros und Compiler, Quellcode zu kompilierten Codezuordnung zu verfolgen, so dass es nicht immer möglich ist, Single-Stepping in Quellcode zu tun.

Offensichtliche Option ist, Einzelschritt in Makroexpanded-Code zu tun. Der Compiler sieht bereits eine endgültige, erweiterte Version des Codes und kann Quellcode für das Maschinencode-Mapping verfolgen.

Andere Option ist die Tatsache, dass Lisp Ausdrücke während der Manipulation Identität haben. Wenn das Makro einfach ist und einfach Code in eine Schablone destrukturiert und einfügt, dann sind einige Ausdrücke des erweiterten Codes identisch (in Bezug auf den EQ-Vergleich) zu Ausdrücken, die aus dem Quellcode gelesen wurden. In diesem Fall kann der Compiler einige Ausdrücke vom erweiterten Code zum Quellcode zuordnen.

2

Sie sollten wirklich in die Art der Unterstützung suchen, die Racket zum Debuggen von Code mit Makros hat. Diese Unterstützung hat zwei Aspekte, wie Ken erwähnt. Auf der einen Seite gibt es das Problem der Fehlersuche Makros: in Common Lisp ist der beste Weg, um Makroformen nur manuell zu erweitern. Mit CPP ist die Situation ähnlich, aber primitiver - Sie führen den Code nur durch die CPP-Erweiterung und überprüfen das Ergebnis. Beide sind jedoch nicht ausreichend für komplexere Makros, und dies war die Motivation dafür, einen macro debugger im Racket zu haben - er zeigt Ihnen die einzelnen Schritte der Syntaxerweiterung mit zusätzlichen Gui-basierten Anzeigen für Dinge wie gebundene Bezeichner usw.

Auf der Seite von mit Makros, war Racket immer fortgeschrittener als andere Scheme und Lisp-Implementierungen. Die Idee ist, dass jeder Ausdruck (als ein syntaktisches Objekt) der Code plus zusätzliche Daten ist, die seinen Quellort enthalten. Wenn ein Formular ein Makro ist, hat der erweiterte Code, der Teile hat, die aus dem Makro stammen, den richtigen Quellort - von der Definition des Makros und nicht von seiner Verwendung (wo die Formulare nicht wirklich vorhanden sind). Einige Scheme- und Lisp-Implementierungen implementieren eine begrenzte dafür unter Verwendung der Identität von Unterformularen, wie dmitry-vk erwähnt.

+0

Aber das Mappen von kompiliertem Code zurück zu Makro löst das folgende Problem nicht: Wenn Makro Code basierend auf deklarativen Eingaben generiert (zB Makro, das Parser aus kontextfreier Grammatik generiert), wäre es sehr nützlich während des Debuggens zu finden heraus, welcher Teil der Eingabe "aktiv" ist (z. B. welche Grammatikregel wird angepasst, wenn das möglich ist). Dies würde erfordern, dass der Makroschreiber explizit angibt, welche Teile der erzeugten Codekarte welchen Teilen der Makroeingabe entsprechen. Haben Racket-Makros solche Fähigkeiten? Ansonsten entspricht das dem Debuggen von (teilweise) erweitertem Code. –

+0

"Ansonsten entspricht es nur dem Debugging von (teilweise) erweitertem Code." Nicht ich denke, dass ich in diesem Satz falsch liege. Bitte ignoriere es. –

+0

dmity-vk: Richtig - die 'Syntax'-Spezialform in Racket kümmert sich im Wesentlichen darum, Code-Teile des Makro-Benutzers mit Code-Stücken aus dem Makro selbst zu kombinieren und sicherzustellen, dass der Quellort auf den resultierenden Formularen liegt in allen Formen korrekt. –

0

Die einfache Antwort ist, dass es kompliziert ist ;-) Es gibt verschiedene Dinge, die dazu beitragen, ein Programm zu debuggen, und noch mehr für die Verfolgung von Makros.

In C und C++ wird der Präprozessor verwendet, um Makros zu erweitern und in tatsächlichen Quellcode zu integrieren. Die Originaldateinamen und Zeilennummern werden in dieser erweiterten Quelldatei mithilfe von #line-Direktiven verfolgt.

http://msdn.microsoft.com/en-us/library/b5w2czay(VS.80).aspx

Wenn ein C oder C++ Programm mit Debugging kompiliert wird, erzeugt der Assembler zusätzliche Informationen in der Objektdatei, die Sourceleitungen verfolgt, Symbolnamen, Typdeskriptoren usw.

http://sources.redhat.com/gdb/onlinedocs/stabs.html

Das Betriebssystem verfügt über Funktionen, die es einem Debugger ermöglichen, eine Verbindung zu einem Prozess herzustellen und die Prozessausführung zu steuern. Pausieren, Einzelschritt usw.

Wenn ein Debugger an das Programm angehängt ist, werden der Prozessstapel und der Programmzähler zurück in symbolische Form übersetzt, indem die Bedeutung von Programmadressen in den Debuginformationen nachgeschlagen wird.

Dynamische Sprachen werden normalerweise in einer virtuellen Maschine ausgeführt, unabhängig davon, ob es sich um einen Interpreter oder eine Bytecode-VM handelt. Es ist die VM, die Hooks zur Verfügung stellt, damit ein Debugger den Programmablauf steuern und den Programmstatus prüfen kann.