Antworten:
Ein weiterer Grund für Compiler, Assemblys anstatt des richtigen Maschinencodes zu erstellen, sind:
add eax,2
kann nach 83 c0 02
oder nach übersetzt werden 66 83 c0 02
, je nach zuletzt aufgetretener Direktive wie use16
.
Ein Compiler konvertiert normalerweise Code auf hoher Ebene direkt in Maschinensprache, kann jedoch modular aufgebaut werden, sodass ein Back-End Maschinencode und der andere Assemblycode (wie GCC) ausgibt. Die Codegenerierungsphase erzeugt "Code", der eine interne Darstellung des Maschinencodes ist, der dann in ein verwendbares Format wie Maschinensprache oder Assembler-Code konvertiert werden muss.
In der Vergangenheit haben einige namhafte Compiler Maschinencode direkt ausgegeben. Es gibt jedoch einige Schwierigkeiten dabei. Im Allgemeinen ist es für jemanden, der versucht zu bestätigen, dass ein Compiler ordnungsgemäß funktioniert, einfacher, die Ausgabe des Assemblycodes zu überprüfen als den Maschinencode. Außerdem ist es möglich (und historisch üblich), einen C- oder Pascal-Compiler mit einem Durchgang zu verwenden, um eine Assembler-Datei zu erstellen, die dann mit einem Assembler mit zwei Durchgängen verarbeitet werden kann. Das direkte Generieren von Code erfordert entweder die Verwendung eines C- oder Pascal-Compilers mit zwei Durchläufen oder die Verwendung eines Compilers mit einem Durchlauf, gefolgt von einer Methode zum Zurück-Patchen von Vorwärtssprungadressen [wenn eine Laufzeitumgebung die Größe eines gestarteten Programms in einem bereitstellt fester platz, Ein Compiler könnte eine Liste von Patches am Ende des Codes schreiben und den Startcode veranlassen, diese Patches zur Laufzeit anzuwenden. Ein solcher Ansatz würde die ausführbare Größe um etwa vier Bytes pro Patch-Punkt erhöhen, aber die Geschwindigkeit der Programmerstellung verbessern.
Wenn der Compiler schnell ausgeführt werden soll, kann die direkte Codegenerierung gut funktionieren. Bei den meisten Projekten sind die Kosten für das Generieren und Assemblieren des Assembler-Codes heutzutage jedoch kein großes Problem. Die Tatsache, dass Compiler Code in einer Form produzieren, die gut mit Code anderer Compiler interagieren kann, ist im Allgemeinen ein hinreichender Vorteil, um die Erhöhung der Kompilierungszeiten zu rechtfertigen.
Sogar Plattformen, die denselben Befehlssatz verwenden, können unterschiedliche verschiebbare Objektdateiformate aufweisen. Ich kann an "a.out" (frühes UNIX), OMF, MZ (MS-DOS EXE), NE (16-Bit Windows), COFF (UNIX System V), Mach-O (OS X und iOS) und denken ELF (Linux und andere) sowie Varianten davon wie XCOFF (AIX), ECOFF (SGI) und COFF-basierte Portable Executable (PE) unter 32-Bit-Windows. Ein Compiler, der Assemblersprache erzeugt, muss nicht viel über Objektdateiformate wissen, sodass Assembler und Linker dieses Wissen in einem separaten Prozess zusammenfassen können.
Siehe auch Unterschied zwischen OMF und COFF bei Stapelüberlauf.
Normalerweise arbeiten Compiler intern mit Folgen von Anweisungen. Jeder Befehl wird durch eine Datenstruktur dargestellt, die den Operationsnamen, die Operanden usw. darstellt. Wenn die Operanden Adressen sind, sind diese Adressen normalerweise symbolische Referenzen, keine konkreten Werte.
Die Ausgabe von Assembler ist relativ einfach. Es geht so ziemlich darum, die interne Datenstruktur des Compilers in eine Textdatei in einem bestimmten Format zu kopieren. Die Assembler-Ausgabe ist auch relativ einfach zu lesen, was nützlich ist, wenn Sie überprüfen müssen, was der Compiler tut.
Die Ausgabe von Binärobjektdateien ist erheblich aufwändiger. Der Compiler-Schreiber muss wissen, wie alle Anweisungen codiert sind (was auf manchen CPUS-Systemen alles andere als trivial sein kann). Er muss einige symbolische Verweise auf relative Programmzähleradressen und andere in eine Form von Metadaten in der Binärobjektdatei konvertieren . Sie müssen alles in einem Format ausschreiben, das sehr systemspezifisch ist.
Ja, Sie können absolut einen Compiler erstellen, der binäre Objekte direkt ausgibt, ohne den Assembler als Zwischenschritt ausschreiben zu müssen. Die Frage bei der Softwareentwicklung ist, ob die Reduzierung der Kompilierungszeit die zusätzliche Entwicklungs- und Wartungsarbeit wert ist.
Der mir vertraute Compiler (freepascal) kann Assembler auf allen Plattformen ausgeben, aber nur binäre Objekte direkt auf einer Teilmenge von Plattformen ausgeben.
Ein Compiler sollte in der Lage sein, zusätzlich zu dem normalen verschiebbaren Code eine Assembler-Ausgabe zu erzeugen, was dem Programmierer zugute kommt.
Einmal habe ich den Fehler in einem C-Programm, das unter Unix System V auf einem LSI-11-Computer ausgeführt wird, einfach nicht gefunden. Nichts schien zu funktionieren. Schließlich ließ ich verzweifelt den protable C-Compiler eine Assembler-Version seiner Übersetzung ausscheiden. Ich hatte endlich den Bug gefunden! Der Compiler hat mehr Register zugewiesen, als auf der Maschine vorhanden waren! (Der Compiler hat die Register R0 bis R8 auf einer Maschine mit nur den Registern R0 bis R7 zugewiesen.) Ich habe es geschafft, den Fehler im Compiler zu umgehen, und mein Programm hat funktioniert.
Ein weiterer Vorteil der Assembler-Ausgabe ist die Verwendung von "Standard" -Bibliotheken, die andere Parameterübergabeprotokolle verwenden. Spätere C-Compiler ermöglichen es mir, das Protokoll mit einem Parameter festzulegen ("Pascal" würde den Compiler veranlassen, die Parameter in der angegebenen Reihenfolge hinzuzufügen, im Gegensatz zum C-Standard zum Umkehren der Reihenfolge).
Ein weiterer Vorteil ist, dass der Programmierer sehen kann, was für eine entsetzliche Arbeit sein Compiler leistet. Eine einfache C-Anweisung benötigt ungefähr 44 Maschinenbefehle. Werte werden aus dem Speicher geladen und dann schnell verworfen. etc, etc, etc ...
Ich persönlich glaube, dass es wirklich dumm ist, einen Compiler anstelle eines verschiebbaren Objektmoduls zu haben. Während der Kompilierung Ihres Programms sammelt der Compiler viele Informationen über Ihr Programm. In der Regel werden alle diese Informationen in einer sogenannten Symboltabelle gespeichert. Nach dem Ausscheiden des Assembler-Codes wirft es alle diese Informationstabelle. Der Assembler untersucht dann den ausgeschiedenen Code und sammelt einige der Informationen, die der Compiler bereits hatte, erneut. Assembler wissen jedoch nichts über If-Anweisungen von For-Anweisungen oder While-Anweisungen. All diese Informationen fehlen also. Anschließend erstellt der Assembler das verschiebbare Objektmodul, das der Compiler nicht erstellt hat.
Warum???