Produziert ein Interpreter Maschinencode?


42

Ich beschäftige mich intensiv mit den Themen von Übersetzern und Dolmetschern. Ich möchte überprüfen, ob mein Grundverständnis stimmt. Nehmen wir also Folgendes an:

Ich habe eine Sprache namens "Foobish" und ihre Stichwörter sind

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Wenn ich also 10 Mal auf die Konsole drucken möchte, würde ich schreiben

OUTPUT 'Hello World', 10;

Hallo World.foobish-Datei.

Jetzt schreibe ich einen Dolmetscher in der Sprache meiner Wahl - in diesem Fall C #:

using System;

namespace FoobishInterpreter
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            analyseAndTokenize(Hello World.foobish-file)//Pseudocode
            int repeats = Token[1];
            string outputString = Token[0];
            for (var i = 0; i < repeats; i++)
            {
                Console.WriteLine(outputString);
            }
        }
    }
}

Auf einer sehr einfachen Dolmetscherebene würde der Dolmetscher die Skriptdatei usw. analysieren und die Fobish-Sprache in der Art und Weise der Implementierung des Dolmetschers ausführen.

Würde ein Compiler eine Maschinensprache erstellen, die direkt auf der physischen Hardware ausgeführt wird?

Ein Interpreter erzeugt also keine Maschinensprache, aber macht ein Compiler dies für seine Eingabe?

Habe ich grundsätzliche Missverständnisse in Bezug auf die Arbeitsweise von Compilern und Dolmetschern?


21
Was glaubst du, macht der C # -Compiler? Als Hinweis, es wird kein Maschinencode erzeugt.
Philip Kendall

3
Ein Java-Compiler erzeugt Code für die JVM. Die Zielmaschine eines Compilers kann also eine virtuelle Maschine sein, die nicht direkt von der Hardware ausgeführt wird. Der Hauptunterschied zwischen Interpreter und Compiler besteht darin, dass ein Compiler zuerst den gesamten Quellcode prüft und in eine Zielmaschinensprache übersetzt. Dieser kompilierte Code wird dann von der Maschine ausgeführt, für die er bestimmt war. Auf der anderen Seite übersetzt ein Interpreter Teile Ihres Programms im laufenden Betrieb und führt sie aus.
Giorgio

@ Giorgio: Du meinst, wie ein JIT?
Robert Harvey

2
@RobertHarvey: Ich meinte den Java Compiler (javac): Soweit ich weiß, produziert er Bytecode für die JVM. Und wieder AFAIK, die JIT kompiliert später (zur Laufzeit) einen Bytecode, der sehr oft in der Maschinensprache verwendet wird.
Giorgio

4
Ein Compiler bedeutet Übersetzen. Es kann alle Arten von Sprache ausgeben: c, Assembly, Javascript, Maschinencode.
Esben Skov Pedersen

Antworten:


77

Die Begriffe "Interpreter" und "Compiler" sind viel unschärfer als früher. Vor vielen Jahren war es üblich, dass Compiler Maschinencode für eine spätere Ausführung produzierten, während Interpreter den Quellcode mehr oder weniger direkt "ausführten". Also waren diese beiden Begriffe damals gut verstanden.

Aber heute gibt es viele Variationen bei der Verwendung von "Compiler" und "Interpreter". Beispielsweise "kompiliert" VB6 Bytecode (eine Form von Intermediate Language ), der dann von der VB-Runtime "interpretiert" wird. Ein ähnlicher Prozess findet in C # statt, das CIL erzeugt , das dann von einem Just-In-Time-Compiler (JIT) ausgeführt wird , der früher als Interpreter gedacht gewesen wäre. Sie können die Ausgabe des JIT in eine tatsächliche ausführbare Binärdatei "einfrieren", indem Sie NGen.exe verwenden , dessen Produkt früher das Ergebnis eines Compilers gewesen wäre .

Die Antwort auf Ihre Frage ist also bei weitem nicht mehr so ​​einfach wie früher.

Weitere Lesungen
Compiler vs. Dolmetscher auf Wikipedia


6
@Giorgio: Die meisten Interpreter führen heutzutage nicht mehr den Quellcode aus, sondern die Ausgabe eines AST oder ähnliches. Compiler haben einen ähnlichen Prozess. Die Unterscheidung ist bei weitem nicht so eindeutig, wie Sie denken.
Robert Harvey

5
"Sie können die Ausgabe des JIT in eine tatsächliche ausführbare Binärdatei" einfrieren ", indem Sie NGen.exe verwenden, dessen Produkt früher das Ergebnis eines Compilers gewesen wäre.": Aber es ist immer noch das Ergebnis eines Compilers (nämlich des Just-in-Time-Compilers). Es spielt keine Rolle, wann der Compiler ausgeführt wird, sondern was er tut. Ein Compiler nimmt als Eingabe eine Darstellung eines Codeteils und gibt eine neue Darstellung aus. Ein Interpreter gibt das Ergebnis der Ausführung dieses Codes aus. Dies sind zwei verschiedene Prozesse, egal wie Sie sie mischen und wann Sie was ausführen.
Giorgio

4
"Compiler" ist einfach der Begriff, den sie gewählt haben, um ihn an GCC anzuhängen. Sie haben sich dafür entschieden, NGen nicht als Compiler zu bezeichnen, obwohl es Maschinencode erzeugt. Stattdessen wurde der Begriff dem vorherigen Schritt angehängt, der möglicherweise als Interpreter bezeichnet werden könnte, obwohl er Maschinencode erzeugt (einige Interpreter tun dies auch). Mein Punkt ist, dass es heutzutage kein verbindliches Prinzip gibt, auf das Sie sich berufen können, um etwas definitiv als Compiler oder Interpreter zu bezeichnen.
Robert Harvey

4
Nach meinem eingeschränkten Verständnis sind x86-CPUs heutzutage ohnehin auf halbem Weg zu hardwarebasierten JIT-Engines geworden.
Leushenko

4
@RobertHarvey Obwohl ich zustimme, dass es keine klare Trennung zwischen den in einem Interpreter und einem Compiler verwendeten Techniken gibt, gibt es eine ziemlich klare Trennung in der Funktion: Wenn das Ergebnis der Ausführung eines bestimmten Tools mit einem Programmcode als Eingabe die Ausführung davon ist Programm ist das Tool ein Interpreter. Wenn das Ergebnis die Ausgabe einer Übersetzung des Programms in eine weniger abstrakte Form ist, handelt es sich um einen Compiler. Wenn das Ergebnis in eine abstraktere Form übersetzt wird, ist es ein Dekompilierer. Fälle, in denen mehr als eines dieser Ergebnisse nicht eindeutig ist.
Jules

34

Die folgende Zusammenfassung basiert auf "Compilers, Principles, Techniques, & Tools" von Aho, Lam, Sethi, Ullman (Pearson International Edition, 2007), Seite 1, 2, mit einigen eigenen Ideen.

Die beiden Grundmechanismen zur Abarbeitung eines Programms sind Kompilierung und Interpretation .

Beim Kompilieren wird ein Quellprogramm in einer bestimmten Sprache als Eingabe verwendet und ein Zielprogramm in einer Zielsprache ausgegeben.

source program --> | compiler | --> target program

Wenn es sich bei der Zielsprache um Maschinencode handelt, kann dieser direkt auf einem bestimmten Prozessor ausgeführt werden:

input --> | target program | --> output

Beim Kompilieren wird das gesamte Eingabeprogramm (oder Modul) gescannt und übersetzt, ohne dass es ausgeführt wird.

Die Interpretation nimmt das Quellprogramm und seine Eingabe als Eingabe und erzeugt die Ausgabe des Quellprogramms

source program, input --> | interpreter | --> output

Bei der Interpretation wird das Programm normalerweise einzeln verarbeitet (analysiert und ausgeführt).

In der Praxis verwenden viele Sprachprozessoren eine Mischung aus beiden Ansätzen. Beispielsweise werden Java-Programme zunächst in ein Zwischenprogramm (Bytecode) übersetzt (kompiliert):

source program --> | translator | --> intermediate program

Die Ausgabe dieses Schritts wird dann von einer virtuellen Maschine ausgeführt (interpretiert):

intermediate program + input --> | virtual machine | --> output

Um die Sache noch komplizierter zu machen, kann die JVM zur Laufzeit eine Just-in-Time-Kompilierung durchführen, um Byte-Code in ein anderes Format zu konvertieren, das dann ausgeführt wird.

Auch wenn Sie in Maschinensprache kompilieren, wird Ihre Binärdatei von einem Interpreter ausgeführt, der vom zugrunde liegenden Prozessor implementiert wird. Daher verwenden Sie auch in diesem Fall eine Mischung aus Kompilierung und Interpretation.

Reale Systeme verwenden also eine Mischung aus beidem. Daher ist es schwierig zu sagen, ob ein bestimmter Sprachprozessor ein Compiler oder ein Interpreter ist, da er wahrscheinlich beide Mechanismen in verschiedenen Phasen seiner Verarbeitung verwendet. In diesem Fall wäre es wahrscheinlich sinnvoller, einen anderen, neutraleren Begriff zu verwenden.

Dennoch sind Kompilierung und Interpretation zwei unterschiedliche Arten der Verarbeitung, wie in den obigen Diagrammen beschrieben.

Um die ersten Fragen zu beantworten.

Ein Compiler würde eine Maschinensprache erstellen, die direkt auf der physischen Hardware läuft?

Nicht unbedingt übersetzt ein Compiler ein Programm, das für eine Maschine M1 geschrieben wurde, in ein äquivalentes Programm, das für eine Maschine M2 geschrieben wurde. Die Zielmaschine kann in Hardware implementiert sein oder eine virtuelle Maschine sein. Konzeptionell gibt es keinen Unterschied. Der wichtige Punkt ist, dass ein Compiler einen Teil des Codes betrachtet und in eine andere Sprache übersetzt, ohne ihn auszuführen.

Ein Interpreter erzeugt also keine Maschinensprache, ein Compiler jedoch für seine Eingabe?

Wenn Sie durch das Produzieren auf die Ausgabe verweisen, dann erzeugt ein Compiler ein Zielprogramm, das möglicherweise in Maschinensprache verfasst ist, ein Interpreter jedoch nicht.


7
Mit anderen Worten: Ein Interpreter nimmt ein Programm P und erzeugt seine Ausgabe O, ein Compiler nimmt P und erzeugt ein Programm P ', das O ausgibt; Interpreter enthalten häufig Komponenten, die Compiler sind (z. B. zu einem Bytecode, einer Zwischendarstellung oder JIT-Maschinenbefehlen), und ebenso kann ein Compiler einen Interpreter enthalten (z. B. zum Auswerten von Compilerzeitberechnungen).
Jon Purdy

"Ein Compiler kann einen Interpreter enthalten (z. B. zum Auswerten von Compilerzeitberechnungen)": Guter Punkt. Ich denke, Lisp-Makros und C ++ - Vorlagen könnten auf diese Weise vorverarbeitet werden.
Giorgio

Noch einfacher: Der C-Präprozessor kompiliert C-Quellcode mit CPP-Anweisungen in einfaches C und enthält einen Interpreter für boolesche Ausdrücke wie z defined A && !defined B.
Jon Purdy

@JonPurdy Ich würde dem zustimmen, aber ich würde auch eine Klasse hinzufügen, "traditionelle Interpreter", die keine Zwischenrepräsentationen verwenden, die über eine möglicherweise mit Token versehene Version der Quelle hinausgehen. Beispiele wären Shells, viele BASICs, klassisches Lisp, Tcl vor 8.0 und bc.
Hobbs

1
@naxa - siehe Lawrence Antwort und Paul Draper Kommentare zu Arten von Compiler. Ein Assembler ist eine spezielle Art von Compiler, bei dem (1) die Ausgabesprache für die direkte Ausführung durch eine Maschine oder eine virtuelle Maschine vorgesehen ist und (2) eine sehr einfache Eins-zu-Eins-Entsprechung zwischen Eingabeanweisungen und Ausgabeanweisungen besteht.
Jules

22

Ein Compiler würde Maschinensprache erstellen

Nein. Ein Compiler ist einfach ein Programm, das ein in Sprache A geschriebenes Programm als Eingabe verwendet und ein semantisch äquivalentes Programm in Sprache B als Ausgabe erzeugt . Sprache B kann alles sein, es muss nicht Maschinensprache sein.

Ein Compiler kann von einer Hochsprache zu einer anderen Hochsprache kompilieren (z. B. GWT, das Java zu ECMAScript kompiliert), von einer Hochsprache zu einer Niedrigsprache kompilieren (z. B. Gambit, das Schema zu C kompiliert). von einer Hochsprache zu Maschinencode (z. B. GCJ, der Java zu systemeigenem Code kompiliert), von einer Niedrigsprache zu einer Hochsprache (z. B. Clue, der C zu Java, Lua, Perl, ECMAScript und Common kompiliert Lisp), von einer einfachen Sprache zu einer anderen einfachen Sprache (z. B. dem Android SDK, das JVML-Bytecode zu Dalvik-Bytecode kompiliert), von einer einfachen Sprache zu Maschinencode (z. B. dem C1X-Compiler, der Teil von HotSpot ist, der JVML-Bytecode in Maschinencode übersetzt), Maschinencode in eine Hochsprache (ein beliebiger sogenannter "Decompiler", auch Emscripten, der LLVM-Maschinencode in ECMAScript übersetzt),Maschinencode in eine einfache Sprache (z. B. der JIT-Compiler in JPC, der nativen x86-Code in JVML-Bytecode kompiliert) und nativer Code in nativen Code (z. B. der JIT-Compiler in PearPC, der nativen PowerPC-Code in nativen x86-Code kompiliert).

Beachten Sie auch, dass "Maschinencode" aus mehreren Gründen ein sehr verschwommener Begriff ist. Beispielsweise gibt es CPUs, die JVM-Bytecode nativ ausführen, und es gibt Software-Interpreter für x86-Maschinencode. Also, was macht einen "nativen Maschinencode" aus, aber nicht den anderen? Außerdem ist jede Sprache Code für eine abstrakte Maschine für diese Sprache.

Es gibt viele spezielle Namen für Compiler, die spezielle Funktionen ausführen. Trotz der Tatsache, dass es sich um spezialisierte Namen handelt, handelt es sich bei allen immer noch um Compiler, nur um spezielle Arten von Compilern:

  • Wenn Sprache A ungefähr auf der gleichen Abstraktionsebene wie Sprache B wahrgenommen wird , kann der Compiler als Transpiler bezeichnet werden (z. B. ein Ruby-zu-ECMAScript-Transpiler oder ein ECMAScript2015-zu-ECMAScript5-Transpiler).
  • Wenn Sprache A auf einer niedrigeren Abstraktionsebene als Sprache B wahrgenommen wird , kann der Compiler als Dekompiler bezeichnet werden (z. B. ein x86-Maschinencode-zu-C-Dekompiler).
  • Wenn Sprache A == Sprache B , wird der Compiler möglicherweise als Optimierer , Verschleierer oder Minifizierer bezeichnet (abhängig von der jeweiligen Funktion des Compilers).

was läuft direkt auf der physikalischen hardware?

Nicht unbedingt. Es kann in einem Interpreter oder in einer VM ausgeführt werden. Es könnte weiter in eine andere Sprache übersetzt werden.

Ein Interpreter erzeugt also keine Maschinensprache, ein Compiler jedoch für seine Eingabe?

Ein Dolmetscher produziert nichts. Es wird nur das Programm ausgeführt.

Ein Compiler erzeugt etwas, aber es muss nicht unbedingt Maschinensprache sein, es kann jede Sprache sein. Es kann sogar die gleiche Sprache wie die Eingabesprache sein! Beispielsweise verfügt Supercompilers, LLC über einen Compiler, der Java als Eingabe und optimiertes Java als Ausgabe verwendet. Es gibt viele ECMAScript-Compiler, die ECMAScript als Eingabe verwenden und optimiertes, minimiertes und verschleiertes ECMAScript als Ausgabe erzeugen.


Das könnte Sie auch interessieren:


16

Ich denke, Sie sollten den Begriff "Compiler versus Interpreter" gänzlich streichen, da dies eine falsche Zweiteilung ist.

  • Ein Compiler ist ein Transformator : Er transformiert ein in einer Quellsprache geschriebenes Computerprogramm und gibt ein Äquivalent in einer Zielsprache aus . Normalerweise ist die Ausgangssprache eine höhere Sprache als die Zielsprache - und wenn es umgekehrt ist, bezeichnen wir diese Art von Transformator häufig als Dekompilierer .
  • Ein Interpreter ist eine Ausführungsmaschine . Es führt ein Computerprogramm aus, das in einer Sprache gemäß der Spezifikation dieser Sprache geschrieben ist. Wir verwenden meistens den Begriff für Software (aber in gewisser Weise kann eine klassische CPU als hardwarebasierter "Interpreter" für ihren Maschinencode angesehen werden).

Das gemeinsame Wort, um eine abstrakte Programmiersprache in der realen Welt nützlich zu machen, ist Implementierung .

In der Vergangenheit bestand eine Programmiersprachenimplementierung häufig nur aus einem Compiler (und der CPU, für die der Code generiert wurde) oder nur aus einem Interpreter. Es sah also so aus, als ob sich diese beiden Arten von Tools gegenseitig ausschließen. Heute kann man deutlich sehen, dass dies nicht der Fall ist (und es war noch nie so). Wenn Sie versuchen, den Namen "Compiler" oder "Interpreter" in eine ausgefeilte Programmiersprache umzuwandeln, führt dies häufig zu nicht eindeutigen oder inkonsistenten Ergebnissen.

Eine einzelne Programmiersprachenimplementierung kann eine beliebige Anzahl von Compilern und Interpreten , häufig in mehreren Formen (eigenständig, im laufenden Betrieb), eine beliebige Anzahl anderer Tools wie statische Analysatoren und Optimierer sowie eine beliebige Anzahl von Schritten umfassen. Es kann sogar vollständige Implementierungen einer beliebigen Anzahl von Zwischensprachen enthalten (die nicht mit der zu implementierenden zusammenhängen können).

Beispiele für Implementierungsschemata sind:

  • AC-Compiler, der C in x86-Maschinencode umwandelt, und eine x86-CPU, die diesen Code ausführt.
  • AC-Compiler, der C in LLVM-IR umwandelt, ein LLVM-Backend-Compiler, der LLVM-IR in x86-Maschinencode umwandelt, und eine x86-CPU, die diesen Code ausführt.
  • AC-Compiler, der C in LLVM-IR umwandelt, und ein LLVM-Interpreter, der LLVM-IR ausführt.
  • Ein Java-Compiler, der Java in JVM-Bytecode umwandelt, und eine JRE mit einem Interpreter, der diesen Code ausführt.
  • Ein Java-Compiler, der Java in JVM-Bytecode umwandelt, und eine JRE mit einem Interpreter, der einige Teile dieses Codes ausführt, und einem Compiler, der andere Teile dieses Codes in x86-Maschinencode umwandelt, sowie einer x86-CPU, die diesen Code ausführt.
  • Ein Java-Compiler, der Java in JVM-Bytecode umwandelt, und eine ARM-CPU, die diesen Code ausführt.
  • AC # -Compiler, der C # in CIL umwandelt, eine CLR mit einem Compiler, der CIL in x86-Maschinencode umwandelt, und einer x86-CPU, die diesen Code ausführt.
  • Ein Ruby-Interpreter, der Ruby ausführt.
  • Eine Ruby-Umgebung mit einem Interpreter, der Ruby ausführt, und einem Compiler, der Ruby in x86-Maschinencode umwandelt, sowie einer x86-CPU, die diesen Code ausführt.

...und so weiter.


+1, um darauf hinzuweisen, dass selbst für die Zwischendarstellung konzipierte Codierungen (z. B. Java-Bytecode) Hardware-Implementierungen aufweisen können.
Jules

7

Während die Grenzen zwischen Compilern und Interpreten im Laufe der Zeit verschwommen sind, kann man immer noch eine Grenze zwischen ihnen ziehen, indem man sich die Semantik dessen ansieht, was das Programm tun soll und was der Compiler / Interpreter tut.

Ein Compiler generiert ein anderes Programm (normalerweise in einer niedrigeren Sprache wie Maschinencode), das, wenn dieses Programm ausgeführt wird, das tut, was Ihr Programm tun soll.

Ein Dolmetscher wird tun, was Ihr Programm tun soll.

Mit diesen Definitionen sind die Stellen, an denen es unscharf wird, die Fälle, in denen Ihr Compiler / Interpreter je nach Ihrer Sichtweise unterschiedliche Aktionen ausführen kann. Zum Beispiel nimmt Python Ihren Python-Code und kompiliert ihn in einen kompilierten Python-Bytecode. Wenn dieser Python-Bytecode über einen Python-Bytecode- Interpreter ausgeführt wird , wird das getan, was Ihr Programm tun sollte. In den meisten Situationen denken die Python-Entwickler jedoch, dass beide Schritte in einem großen Schritt ausgeführt werden. Daher wird der CPython- Interpreter als Interpretation des Quellcodes angesehen und die Tatsache, dass er auf diesem Weg kompiliert wurde , wird als Implementierungsdetail angesehen . Auf diese Weise ist alles eine Frage der Perspektive.


5

Hier ist eine einfache konzeptionelle Disambiguierung zwischen Compilern und Interpreten.

Betrachten Sie 3 Sprachen: Programmiersprache , P (was das Programm geschrieben ist); Domänensprache D (für das, was mit dem laufenden Programm passiert); und Zielsprache , T (eine dritte Sprache).

Konzeptionell

  • Ein Compiler übersetzt P in T, so dass Sie T (D) auswerten können. wohingegen

  • Ein Interpreter wertet P (D) direkt aus.


1
Die meisten modernen Dolmetscher werten die Ausgangssprache nicht direkt aus, sondern nur eine Zwischenrepräsentation der Ausgangssprache.
Robert Harvey

4
@RobertHarvey Das ändert nichts an der konzeptionellen Unterscheidung zwischen den Begriffen.
Lawrence

1
Was Sie also wirklich als Interpreter bezeichnen, ist der Teil, der die Zwischendarstellung bewertet. Der Teil, der die Zwischendarstellung erstellt, ist Ihrer Definition nach ein Compiler .
Robert Harvey

6
@RobertHarvey Nicht wirklich. Die Begriffe hängen von der Abstraktionsebene ab, auf der Sie arbeiten. Wenn Sie nach unten schauen, kann das Tool alles tun. Nehmen wir an, Sie gehen in ein fremdes Land und bringen einen zweisprachigen Freund Bob mit. Wenn Sie mit den Einheimischen kommunizieren, indem Sie mit Bob sprechen, der wiederum mit den Einheimischen spricht, fungiert Bob als Dolmetscher für Sie (auch wenn er vor dem Sprechen in ihrer Sprache kritzelt). Wenn Sie Bob nach Phrasen fragen und Bob diese in der Fremdsprache schreibt, und Sie mit den Einheimischen kommunizieren, indem Sie sich auf diese Schriften beziehen (nicht Bob), fungiert Bob als Compiler für Sie.
Lawrence

1
Hervorragende Antwort. Bemerkenswert: Heutzutage können Sie "transpiler" hören. Das ist ein Compiler, bei dem P und T ähnliche Abstraktionsebenen sind, um ähnliche zu definieren. (ZB ein ES5 bis ES6 Transpiler.)
Paul Draper
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.