Un interprete produce codice macchina?


42

Studio intensamente gli argomenti di compilatori e interpreti. Voglio verificare se la mia comprensione di base è corretta, quindi supponiamo che:

Ho una lingua chiamata "Foobish" e le sue parole chiave lo sono

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Quindi, se voglio stampare sulla console 10 volte, scriverei

OUTPUT 'Hello World', 10;

Ciao World.foobish-file.

Ora scrivo un interprete nella lingua che preferisco - C # in questo caso:

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);
            }
        }
    }
}

A livello di interprete molto semplice, l'interprete analizzava il file di script, ecc. Ed eseguiva il linguaggio foobish nel modo di implementazione dell'interprete.

Un compilatore creerebbe un linguaggio macchina che gira direttamente sull'hardware fisico?

Quindi un interprete non produce linguaggio macchina, ma un compilatore lo fa per il suo input?

Ho dei malintesi nel modo di base di come funzionano i compilatori e gli interpreti?


21
Cosa pensi che faccia il "compilatore" C #? Come suggerimento, non produce codice macchina.
Philip Kendall,

3
Un compilatore Java produce codice per JVM. Quindi la macchina target di un compilatore può essere una macchina virtuale che non viene eseguita direttamente dall'hardware. La differenza principale tra interprete e compilatore è che un compilatore controlla innanzitutto e traduce l'intero codice sorgente in un linguaggio macchina di destinazione. Questo codice compilato viene quindi eseguito dalla macchina per cui era destinato. D'altra parte, un interprete tradurrà ed eseguirà blocchi del programma al volo.
Giorgio,

@Giorgio: Intendi, come un JIT?
Robert Harvey,

2
@RobertHarvey: intendevo Java Compiler (javac): per quanto ne so produce un bytecode per la JVM. E, ancora una volta AFAIK, la JIT in seguito (in fase di esecuzione) compila alcuni bytecode che viene usato molto spesso nel linguaggio macchina nativo.
Giorgio,

4
un compilatore significa tradurre. Può emettere tutti i tipi di linguaggio: c, assembly, javascript, codice macchina.
Esben Skov Pedersen,

Risposte:


77

I termini "interprete" e "compilatore" sono molto più confusi di prima. Molti anni fa era più comune per i compilatori produrre codice macchina da eseguire in seguito, mentre gli interpreti più o meno "eseguivano" direttamente il codice sorgente. Quindi quei due termini erano ben compresi allora.

Ma oggi ci sono molte varianti sull'uso di "compilatore" e "interprete". Ad esempio, VB6 "compila" in codice byte (una forma di linguaggio intermedio ), che viene quindi "interpretato" da VB Runtime. Un processo simile si svolge in C #, che produce CIL che viene quindi eseguito da un compilatore Just-In-Time (JIT) che, ai vecchi tempi, sarebbe stato pensato come un interprete. È possibile "congelare" l'output di JIT in un vero eseguibile binario utilizzando NGen.exe , il cui prodotto sarebbe stato il risultato di un compilatore ai vecchi tempi.

Quindi la risposta alla tua domanda non è così semplice come una volta.

Ulteriori lettori di lettura
vs. interpreti su Wikipedia


6
@Giorgio: la maggior parte degli interpreti al giorno d'oggi in realtà non esegue il codice sorgente, ma piuttosto l'output di un AST o qualcosa di simile. I compilatori hanno un processo simile. La distinzione non è così chiara come pensi.
Robert Harvey,

5
"Puoi" congelare "l'output di JIT in un vero eseguibile binario usando NGen.exe, il cui prodotto sarebbe stato il risultato di un compilatore ai vecchi tempi.": Ma è ancora oggi il risultato di un compilatore (vale a dire, il compilatore just-in-time). Non importa quando viene eseguito il compilatore, ma cosa fa. Un compilatore accetta come input una rappresentazione di un pezzo di codice e genera una nuova rappresentazione. Un interprete produrrà il risultato dell'esecuzione di quel pezzo di codice. Questi sono due processi diversi, non importa come li mescoli e quando esegui cosa.
Giorgio,

4
"Compilatore" è semplicemente il termine che hanno scelto di associare a GCC. Hanno scelto di non chiamare NGen un compilatore, anche se produce codice macchina, preferendo invece associare quel termine al passaggio precedente, che potrebbe probabilmente essere chiamato un interprete, anche se produce codice macchina (anche alcuni interpreti lo fanno). Il mio punto è che al giorno d'oggi non esiste un principio vincolante che puoi invocare per chiamare definitivamente qualcosa un compilatore o un interprete, diverso da "questo è quello che l'hanno sempre chiamato".
Robert Harvey,

4
In base alla mia comprensione molto limitata, al giorno d'oggi le CPU x86 sono quasi a metà strada da motori JIT basati su hardware, con l'assemblaggio che ha una relazione sempre più sbiadita con ciò che viene esattamente eseguito.
Leushenko,

4
@RobertHarvey mentre concordo sul fatto che non esiste una chiara linea di demarcazione tra le tecniche utilizzate in un interprete e un compilatore, esiste una divisione abbastanza chiara nella funzione: se il risultato dell'esecuzione di un determinato strumento con il codice di un programma come input è l'esecuzione di quello programma, lo strumento è un interprete. Se il risultato è l'output di una traduzione del programma in una forma meno astratta, è un compilatore. Se il risultato è la traduzione in una forma più astratta, si tratta di un decompilatore. Casi in cui più di uno di questi risultati sono ambigui, tuttavia.
Jules,

34

Il riepilogo che fornisco di seguito si basa su "Compilatori, Principi, Tecniche e strumenti", Aho, Lam, Sethi, Ullman, (Pearson International Edition, 2007), pagine 1, 2, con l'aggiunta di alcune mie idee.

I due meccanismi di base per l'elaborazione di un programma sono la compilazione e l' interpretazione .

La compilazione prende come input un programma sorgente in una determinata lingua e produce un programma target in una lingua target.

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

Se la lingua di destinazione è il codice macchina, può essere eseguito direttamente su alcuni processori:

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

La compilazione comporta la scansione e la traduzione dell'intero programma di input (o modulo) e non comporta l'esecuzione.

L'interpretazione prende come input il programma sorgente e il suo input e produce l'output del programma sorgente

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

L'interpretazione di solito implica l'elaborazione (analisi ed esecuzione) del programma un'istruzione alla volta.

In pratica, molti processori di linguaggio usano un mix dei due approcci. Ad esempio, i programmi Java vengono prima tradotti (compilati) in un programma intermedio (codice byte):

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

l'output di questo passaggio viene quindi eseguito (interpretato) da una macchina virtuale:

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

Per complicare ulteriormente le cose, JVM può eseguire una compilazione just-in-time in fase di esecuzione per convertire il codice byte in un altro formato, che viene quindi eseguito.

Inoltre, anche durante la compilazione in linguaggio macchina, esiste un interprete che esegue il file binario che viene implementato dal processore sottostante. Pertanto, anche in questo caso si utilizza un ibrido di compilazione + interpretazione.

Quindi, i sistemi reali usano un mix dei due, quindi è difficile dire se un determinato elaboratore di linguaggio sia un compilatore o un interprete, perché probabilmente utilizzerà entrambi i meccanismi nelle diverse fasi della sua elaborazione. In questo caso sarebbe probabilmente più appropriato usare un altro termine più neutro.

Tuttavia, la compilazione e l'interpretazione sono due distinti tipi di elaborazione, come descritto negli schemi sopra,

Per rispondere alle domande iniziali.

Un compilatore creerebbe un linguaggio macchina che gira direttamente sull'hardware fisico?

Non necessariamente, un compilatore traduce un programma scritto per una macchina M1 in un programma equivalente scritto per una macchina M2. La macchina target può essere implementata in hardware o essere una macchina virtuale. Concettualmente non c'è differenza. Il punto importante è che un compilatore guarda un pezzo di codice e lo traduce in un'altra lingua senza eseguirlo.

Quindi un interprete non produce un linguaggio macchina ma un compilatore lo fa per il suo input?

Se producendo ti riferisci all'output, un compilatore produce un programma target che può essere in linguaggio macchina, un interprete no.


7
In altre parole: un interprete prende un programma P e produce il suo output O, un compilatore prende P e produce un programma P ′ che genera O; gli interpreti spesso includono componenti che sono compilatori (ad esempio, per un bytecode, una rappresentazione intermedia o istruzioni della macchina JIT) e allo stesso modo un compilatore può includere un interprete (ad esempio, per valutare calcoli in fase di compilazione).
Jon Purdy,

"un compilatore può includere un interprete (ad es. per la valutazione di calcoli in fase di compilazione)": buon punto. Suppongo che le macro Lisp e i modelli C ++ potrebbero essere pre-elaborati in questo modo.
Giorgio,

Ancora più semplice, il preprocessore C compila il codice sorgente C con le direttive CPP in C semplice e include un interprete per espressioni booleane come defined A && !defined B.
Jon Purdy,

@JonPurdy Concordo con questo, ma aggiungerei anche una classe, "interpreti tradizionali", che non fanno uso di rappresentazioni intermedie oltre forse una versione tokenizzata della fonte. Esempi potrebbero essere shell, molti BASIC, Lisp classico, Tcl prima di 8.0 e bc.
Hobbs,

1
@naxa - vedi la risposta di Lawrence e i commenti di Paul Draper sui tipi di compilatore. Un assemblatore è un tipo speciale di compilatore in cui (1) il linguaggio di output è destinato all'esecuzione diretta da parte di una macchina o macchina virtuale e (2) esiste una corrispondenza one-to-one molto semplice tra istruzioni di input e istruzioni di output.
Jules,

22

Un compilatore creerebbe il linguaggio macchina

No. Un compilatore è semplicemente un programma che prende come ingresso un programma scritto in linguaggio A e produce come uscita un programma semanticamente equivalente in linguaggio B . La lingua B può essere qualsiasi cosa, non deve essere un linguaggio macchina.

Un compilatore può compilare da un linguaggio di alto livello in un altro linguaggio di alto livello (ad esempio GWT, che compila Java in ECMAScript), da un linguaggio di alto livello a un linguaggio di basso livello (ad esempio Gambit, che compila Scheme in C), da un linguaggio di alto livello a un codice macchina (ad esempio GCJ, che compila Java in codice nativo), da un linguaggio di basso livello a un linguaggio di alto livello (ad esempio Clue, che compila C in Java, Lua, Perl, ECMAScript e Common Lisp), da una lingua di basso livello a un'altra lingua di basso livello (ad esempio l'SDK di Android, che compila il bytecode JVML in bytecode Dalvik), da una lingua di basso livello a un codice macchina (ad esempio il compilatore C1X che fa parte di HotSpot, che compila il bytecode JVML in codice macchina), il codice macchina in un linguaggio di alto livello (qualsiasi cosiddetto "decompilatore", anche Emscripten, che compila il codice macchina LLVM in ECMAScript),codice macchina in un linguaggio di basso livello (ad esempio il compilatore JIT in JPC, che compila codice nativo x86 in bytecode JVML) e codice nativo in codice nativo (ad esempio il compilatore JIT in PearPC, che compila il codice nativo PowerPC in codice nativo x86).

Si noti inoltre che "codice macchina" è un termine molto confuso per diversi motivi. Ad esempio, ci sono CPU che eseguono nativamente il codice byte JVM e ci sono interpreti software per il codice macchina x86. Quindi, cosa rende un "codice macchina nativo" ma non l'altro? Inoltre, ogni lingua è il codice per una macchina astratta per quella lingua.

Esistono molti nomi specializzati per compilatori che svolgono funzioni speciali. Nonostante si tratti di nomi specializzati, tutti questi sono ancora compilatori, solo tipi speciali di compilatori:

  • se la lingua A è percepita all'incirca allo stesso livello di astrazione della lingua B , il compilatore potrebbe essere chiamato un transpiler (ad esempio un traspiler Ruby-to-ECMAScript o un ECMAScript2015-to-ECMAScript5-transpiler)
  • se la lingua A è percepita a un livello di astrazione di livello inferiore rispetto alla lingua B , il compilatore potrebbe essere chiamato decompilatore (ad esempio un decompilatore da codice macchina x86 a decompilatore C)
  • se la lingua A == lingua B , il compilatore potrebbe essere chiamato un ottimizzatore , un offuscatore o un minificatore (a seconda della particolare funzione del compilatore)

che gira direttamente sull'hardware fisico?

Non necessariamente. Potrebbe essere eseguito in un interprete o in una macchina virtuale. Potrebbe essere ulteriormente compilato in un'altra lingua.

Quindi un interprete non produce un linguaggio macchina ma un compilatore lo fa per il suo input?

Un interprete non produce nulla. Esegue solo il programma.

Un compilatore produce qualcosa, ma non deve necessariamente essere un linguaggio macchina, può essere qualsiasi linguaggio. Può anche essere la stessa lingua della lingua di input! Ad esempio, Supercompilers, LLC ha un compilatore che accetta Java come input e produce Java ottimizzato come output. Esistono molti compilatori ECMAScript che prendono ECMAScript come input e producono ECMAScript ottimizzati, minimizzati e offuscati come output.


Potrebbe interessarti anche:


16

Penso che dovresti abbandonare del tutto la nozione di "compilatore contro interprete", perché è una falsa dicotomia.

  • Un compilatore è un trasformatore : trasforma un programma per computer scritto in una lingua di origine e genera un equivalente in una lingua di destinazione . Di solito, la lingua di origine è di livello superiore rispetto alla lingua di destinazione - e se è il contrario, spesso chiamiamo quel tipo di trasformatore un decompilatore .
  • Un interprete è un motore di esecuzione . Esegue un programma per computer scritto in una lingua, secondo le specifiche di quella lingua. Usiamo principalmente il termine per software (ma in un certo senso, una CPU classica può essere vista come un "interprete" basato su hardware per il suo codice macchina).

La parola collettiva per rendere utile un linguaggio di programmazione astratto nel mondo reale è implementazione .

In passato, un'implementazione del linguaggio di programmazione consisteva spesso solo in un compilatore (e nella CPU per cui generava il codice) o in un semplice interprete - quindi potrebbe sembrare che questi due tipi di strumenti si escludano a vicenda. Oggi puoi vedere chiaramente che non è così (e non è mai stato per cominciare). Prendere una sofisticata implementazione del linguaggio di programmazione e tentare di inserire il nome "compilatore" o "interprete" su di esso, spesso ti porterà a risultati inconcludenti o incoerenti.

Una singola implementazione del linguaggio di programmazione può coinvolgere un numero qualsiasi di compilatori ed interpreti , spesso in più forme (standalone, al volo), qualsiasi numero di altri strumenti, come analizzatori statici e ottimizzatori , e qualsiasi numero di passaggi. Può anche includere intere implementazioni di un numero qualsiasi di lingue intermedie (che potrebbe non essere correlato a quello implementato).

Esempi di schemi di implementazione includono:

  • Compilatore CA che trasforma C in codice macchina x86 e una CPU x86 che esegue quel codice.
  • Compilatore CA che trasforma C in LLVM IR, un compilatore back-end LLVM che trasforma LLVM IR in codice macchina x86 e una CPU x86 che esegue quel codice.
  • Compilatore CA che trasforma C in LLVM IR e un interprete LLVM che esegue LLVM IR.
  • Un compilatore Java che trasforma Java in bytecode JVM e un JRE con un interprete che esegue quel codice.
  • Un compilatore Java che trasforma Java in bytecode JVM e un JRE con un interprete che esegue alcune parti di quel codice e un compilatore che trasforma altre parti di quel codice in codice macchina x86 e una CPU x86 che esegue quel codice.
  • Un compilatore Java che trasforma Java in bytecode JVM e una CPU ARM che esegue quel codice.
  • Compilatore AC # che trasforma C # in CIL, un CLR con un compilatore che trasforma CIL in codice macchina x86 e una CPU x86 che esegue quel codice.
  • Un interprete di Ruby che esegue Ruby.
  • Un ambiente Ruby con un interprete che esegue Ruby e un compilatore che trasforma Ruby in codice macchina x86 e una CPU x86 che esegue quel codice.

...e così via.


+1 per sottolineare che anche le codifiche progettate per la rappresentazione intermedia (ad es. Java bytecode) possono avere implementazioni hardware.
Jules,

7

Mentre le linee tra compilatori e interpreti sono diventate confuse nel tempo, si può ancora tracciare una linea tra loro guardando la semantica di cosa dovrebbe fare il programma e cosa fa il compilatore / interprete.

Un compilatore genererà un altro programma (in genere in una lingua di livello inferiore come il codice macchina) che, se quel programma viene eseguito, farà ciò che il programma dovrebbe fare.

Un interprete farà ciò che il tuo programma dovrebbe fare.

Con queste definizioni, i luoghi in cui diventa confuso sono i casi in cui il tuo compilatore / interprete può essere pensato come fare cose diverse a seconda di come lo guardi. Ad esempio, Python prende il tuo codice Python e lo compila in un bytecode Python compilato. Se questo bytecode Python viene eseguito attraverso un interprete bytecode Python , fa ciò che il programma avrebbe dovuto fare. Nella maggior parte delle situazioni, tuttavia, gli sviluppatori di Python pensano che entrambi questi passaggi vengano eseguiti in un unico grande passaggio, quindi scelgono di pensare all'interprete CPython come a interpretare il loro codice sorgente, e il fatto che sia stato compilato lungo il percorso è considerato un dettaglio di implementazione . In questo modo, è tutta una questione di prospettiva.


5

Ecco una semplice chiarimento concettuale tra compilatori ed interpreti.

Considera 3 lingue: linguaggio di programmazione , P (in cosa è scritto il programma); lingua del dominio , D (per ciò che accade con il programma in esecuzione); e lingua di destinazione , T (qualche terza lingua).

concettualmente,

  • un compilatore traduce P in T in modo da poter valutare T (D); mentre

  • un interprete valuta direttamente P (D).


1
La maggior parte degli interpreti moderni in realtà non valuta direttamente la lingua di partenza, ma piuttosto una rappresentazione intermedia della lingua di partenza.
Robert Harvey,

4
@RobertHarvey Questo non cambia la distinzione concettuale tra i termini.
Lawrence,

1
Quindi ciò a cui ti riferisci davvero come interprete è la parte che valuta la rappresentazione intermedia. La parte che crea la rappresentazione intermedia è un compilatore , secondo la tua definizione.
Robert Harvey,

6
@RobertHarvey Non proprio. I termini dipendono dal livello di astrazione a cui stai lavorando. Se guardi sotto, lo strumento potrebbe fare qualsiasi cosa. Per analogia, supponiamo che tu vada in un paese straniero e porti con te un amico bilingue Bob. Se comunichi con la gente del posto parlando con Bob che a sua volta parla con la gente del posto, Bob funge da interprete per te (anche se scarabocchia nella loro lingua prima di parlare). Se chiedi a Bob delle frasi e Bob le scrive in lingua straniera e comunichi con la gente del posto facendo riferimento a quegli scritti (non Bob), Bob funge da compilatore per te.
Lawrence,

1
Risposta eccellente. Vale la pena notare: al giorno d'oggi potresti sentire "transpiler". Questo è un compilatore in cui P e T sono livelli simili di astrazione, per alcune definizioni di simili. (Ad esempio un transpiler da ES5 a ES6.)
Paul Draper,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.