Könnte eine seltsame Frage sein.
Ein Typ, der einen C ++ - Compiler schreibt (oder eine andere Sprache als VM): Muss er in der Lage sein, rohe Maschinensprache zu lesen / schreiben? Wie funktioniert das?
BEARBEITEN: Ich beziehe mich speziell auf Compiler, die mit Maschinencode kompiliert werden, nicht mit einer anderen Programmiersprache.
compiler
machine-code
Aviv Cohn
quelle
quelle
Antworten:
Nein überhaupt nicht. Es ist durchaus möglich (und oft sogar vorzuziehen), dass Ihr Compiler stattdessen Assembly-Code ausgibt. Der Assembler kümmert sich dann um die Erstellung des eigentlichen Maschinencodes.
Übrigens ist Ihre Unterscheidung zwischen Nicht-VM-Implementierung und VM-Implementierung nicht sinnvoll.
Für den Anfang sind die Verwendung einer VM oder die Vorkompilierung von Maschinencode nur verschiedene Möglichkeiten, eine Sprache zu implementieren. In den meisten Fällen kann eine Sprache mit beiden Strategien implementiert werden. Ich musste tatsächlich einmal einen C ++ - Interpreter verwenden .
Außerdem haben viele VMs wie die JVM einen binären Maschinencode und einen Assembler, genau wie eine gewöhnliche Architektur.
Das LLVM (das von den Clang-Compilern verwendet wird) verdient hier besondere Erwähnung: Es definiert eine VM, für die Anweisungen entweder als Bytecode, Textassemblierung oder als Datenstruktur dargestellt werden können, was die Ausgabe von einem Compiler sehr einfach macht. Obwohl es für das Debuggen nützlich wäre (und um zu verstehen, was Sie tun), müssten Sie nicht einmal die Assemblersprache kennen, sondern nur die LLVM-API.
Das Schöne an der LLVM ist, dass ihre VM nur eine Abstraktion ist und dass der Bytecode normalerweise nicht interpretiert wird, sondern stattdessen transparent JITted. Es ist also durchaus möglich, eine Sprache zu schreiben, die effektiv kompiliert wird, ohne jemals etwas über den Befehlssatz Ihrer CPU wissen zu müssen.
quelle
Nein. Der entscheidende Punkt Ihrer Frage ist, dass die Zusammenstellung ein äußerst weit gefasster Begriff ist. Die Kompilierung kann von jeder Sprache zu jeder Sprache erfolgen. Und Assembly- / Maschinencode ist nur eine der vielen Sprachen für das Kompilierungsziel. Beispielsweise werden Java- und .NET-Sprachen wie C #, F # und VB.NET zu einer Art Zwischencode anstelle von maschinenspezifischem Code kompiliert. Es spielt keine Rolle, ob es dann auf einer VM ausgeführt wird, die Sprache ist noch kompiliert. Es gibt auch die Möglichkeit, in eine andere Sprache zu kompilieren, wie z. B. C. C ist ein sehr beliebtes Kompilierungsziel, und viele Tools tun dies. Und schließlich können Sie ein Tool oder eine Bibliothek verwenden, um die harte Arbeit der Erstellung von Maschinencode für Sie zu erledigen. Es gibt zum Beispiel LLVM, das den Aufwand für die Erstellung eines eigenständigen Compilers reduzieren kann.
Außerdem macht Ihre Bearbeitung keinen Sinn. Es ist wie zu fragen: "Muss jeder Ingenieur verstehen, wie der Motor funktioniert? Und ich frage nach Ingenieuren, die an Motoren arbeiten." Wenn Sie an einem Programm oder einer Bibliothek arbeiten, die einen Maschinencode ausgibt, müssen Sie ihn verstehen. Der Punkt ist, dass Sie so etwas nicht tun müssen, wenn Sie einen Compiler schreiben. Viele Leute haben es vor Ihnen getan, daher müssen Sie ernsthafte Gründe haben, es erneut zu tun.
quelle
Klassischerweise besteht ein Compiler aus drei Teilen: lexikalische Analyse, Analyse und Codegenerierung. Die lexikalische Analyse unterteilt den Programmtext in Schlüsselwörter, Namen und Werte der Sprache. Beim Parsen wird dargestellt, wie die aus der lexikalischen Analyse stammenden Token in syntaktisch korrekten Anweisungen für die Sprache kombiniert werden. Die Codegenerierung verwendet die vom Parser erzeugten Datenstrukturen und übersetzt sie in Maschinencode oder eine andere Darstellung. Heutzutage kann die lexikalische Analyse und Analyse in einem einzigen Schritt kombiniert werden.
Es ist klar, dass die Person, die den Codegenerator schreibt, den Code des Zielcomputers auf einer sehr tiefen Ebene verstehen muss, einschließlich Befehlssätzen, Prozessor-Pipelines und Cache-Verhalten. Andernfalls wären die vom Compiler erstellten Programme langsam und ineffizient. Sie sind möglicherweise in der Lage, Maschinencode zu lesen und zu schreiben, wie er durch Oktal- oder Hexadezimalzahlen dargestellt wird, aber sie schreiben im Allgemeinen Funktionen zum Generieren des Maschinencodes, wobei sie intern auf Tabellen mit Maschinenanweisungen verweisen. Theoretisch wissen die Leute, die den Lexer und den Parser schreiben, möglicherweise nichts über die Generierung des Maschinencodes. In der Tat können Sie mit einigen modernen Compilern Ihre eigenen Codegenerierungsroutinen einbinden, die möglicherweise Maschinencode für eine CPU ausgeben, von der Lexer- und Parser-Autoren noch nie gehört haben.
In der Praxis wissen Compiler-Autoren bei jedem Schritt jedoch viel über verschiedene Prozessorarchitekturen, und dies hilft ihnen beim Entwerfen der Datenstrukturen, die der Codegenerierungsschritt benötigt.
quelle
Vor langer Zeit habe ich einen Compiler geschrieben, der zwischen zwei verschiedenen Shell-Skripten konvertiert hat. Es kam dem Maschinencode nicht nahe.
Ein Compiler-Schreibzugriff muss seine Ausgabe verstehen , aber das ist oft kein Maschinencode.
Die meisten Programmierer werden niemals einen Compiler schreiben, der Maschinencode oder Assemblycode ausgibt, aber benutzerdefinierte Compiler können in vielen Projekten sehr nützlich sein, um andere Ausgaben zu erzeugen.
YACC ist ein solcher Compiler, der keinen Maschinencode ausgibt.
quelle
Sie nicht brauchen , zu beginnen mit einer detaillierten Kenntnis der Semantik des Eingangs- und Ausgangssprachen, aber Sie besser Finish mit einem exquisit detaillierte Kenntnisse von beiden, sonst wird Ihr Compiler wird unusably Buggy. Wenn Ihre Eingabe also C ++ ist und Ihre Ausgabe eine bestimmte Maschinensprache ist, müssen Sie eventuell die Semantik beider kennen.
Hier sind einige der Feinheiten beim Kompilieren von C ++ zu Maschinencode: (Ich bin mir sicher, dass ich noch mehr vergesse.)
Welche Größe wird
int
sein? Die "richtige" Wahl ist hier eine Kunst, die sowohl auf der natürlichen Zeigergröße der Maschine, der Leistung der ALU für verschiedene Größen von Rechenoperationen als auch auf den Entscheidungen basiert, die von vorhandenen Compilern für die Maschine getroffen werden. Hat die Maschine überhaupt 64-Bit-Arithmetik? Wenn nicht, sollte das Hinzufügen von 32-Bit-Ganzzahlen in einen Befehl übersetzt werden, während das Hinzufügen von 64-Bit-Ganzzahlen in einen Funktionsaufruf übersetzt werden sollte, um das 64-Bit-Addieren durchzuführen. Verfügt der Computer über 8-Bit- und 16-Bit-Additionsoperationen oder müssen Sie solche mit 32-Bit-Operationen und Maskierung simulieren (z. B. DEC Alpha 21064)?Welche Aufrufkonvention wird von anderen Compilern, Bibliotheken und Sprachen auf dem Computer verwendet? Werden die Parameter von rechts nach links oder von links nach rechts auf den Stapel verschoben? Gehen einige Parameter in Register, während andere auf den Stapel gehen? Befinden sich Ints und Floats in verschiedenen Registerräumen? Müssen die dem Register zugewiesenen Parameter bei varargs-Aufrufen speziell behandelt werden? Welche Register werden vom Anrufer und welche vom Angerufenen gespeichert? Können Sie Leaf-Call-Optimierungen durchführen?
Was macht jede Schaltanweisung der Maschine? Wenn Sie eine 64-Bit-Ganzzahl um 65 Bit verschieben möchten, was ist das Ergebnis? (Auf vielen Maschinen entspricht das Ergebnis einer Verschiebung um 1 Bit, auf anderen ist das Ergebnis "0".)
Was ist die Semantik der Speicherkonsistenz der Maschine? C ++ 11 verfügt über eine sehr gut definierte Speichersemantik, die in einigen Fällen einige Optimierungen einschränkt, in anderen Fällen jedoch Optimierungen zulässt. Wenn Sie eine Sprache kompilieren, die keine genau definierte Speichersemantik aufweist (wie jede Version von C / C ++ vor C ++ 11 und viele andere zwingende Sprachen), müssen Sie die Speichersemantik im Laufe der Zeit und normalerweise erfinden Sie möchten die Speichersemantik erfinden, die am besten zu Ihrer Maschinensemantik passt.
quelle