¿Un intérprete produce código de máquina?

42

Estudio los temas de compiladores e intérpretes intensivamente. Quiero verificar si mi comprensión básica es correcta, así que supongamos lo siguiente:

Tengo un idioma llamado "Foobish" y sus palabras clave son

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Entonces, si quiero imprimir en la consola 10 veces, escribiría

OUTPUT 'Hello World', 10;

Hola World.foobish-file.

Ahora escribo un intérprete en el idioma de mi elección: C # en este 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);
            }
        }
    }
}

En un nivel de intérprete muy fácil, el intérprete analizaría el archivo de script, etc. y ejecutaría el lenguaje foobish en la forma de la implementación del intérprete.

¿Un compilador crearía un lenguaje de máquina que se ejecute directamente en el hardware físico?

Entonces, un intérprete no produce lenguaje de máquina, pero ¿lo hace un compilador para su entrada?

¿Tengo algún malentendido en la forma básica de cómo funcionan los compiladores y los intérpretes?

Zorro gris
fuente
21
¿Qué crees que hace el "compilador" de C #? Como pista, no produce código de máquina.
Philip Kendall
3
Un compilador de Java produce código para la JVM. Por lo tanto, la máquina de destino de un compilador puede ser una máquina virtual que el hardware no ejecuta directamente. La principal diferencia entre el intérprete y el compilador es que un compilador primero comprueba y traduce todo el código fuente a un lenguaje de máquina de destino. Este código compilado es ejecutado por la máquina para la que fue diseñado. Por otro lado, un intérprete traducirá y ejecutará fragmentos de su programa sobre la marcha.
Giorgio
@ Jorge: ¿Quieres decir, como un JIT?
Robert Harvey
2
@RobertHarvey: me refería al compilador de Java (javac): que yo sepa, produce bytecode para la JVM. Y, nuevamente AFAIK, el JIT más tarde (en tiempo de ejecución) compila algunos códigos de bytes que se usan muy a menudo en lenguaje de máquina nativo.
Giorgio
44
Un compilador significa traducir. Puede emitir todo tipo de lenguaje: c, ensamblaje, javascript, código de máquina.
Esben Skov Pedersen

Respuestas:

77

Los términos "intérprete" y "compilador" son mucho más difusos de lo que solían ser. Hace muchos años, era más común que los compiladores produjeran código de máquina para ejecutarlo más tarde, mientras que los intérpretes más o menos "ejecutaban" el código fuente directamente. Entonces esos dos términos se entendieron bien en ese entonces.

Pero hoy hay muchas variaciones en el uso de "compilador" e "intérprete". Por ejemplo, VB6 "compila" en código de bytes (una forma de lenguaje intermedio ), que luego es "interpretado" por el tiempo de ejecución de VB. Un proceso similar tiene lugar en C #, que produce CIL que luego es ejecutado por un compilador Just-In-Time (JIT) que, en los viejos tiempos, habría sido considerado como un intérprete. Puede "liofilizar" la salida del JIT en un ejecutable binario real utilizando NGen.exe , cuyo producto habría sido el resultado de un compilador en los viejos tiempos.

Por lo tanto, la respuesta a su pregunta no es tan sencilla como lo era antes.

Lecturas adicionales
Compiladores vs. Intérpretes en Wikipedia

Robert Harvey
fuente
66
@Giorgio: La mayoría de los intérpretes actualmente no ejecutan el código fuente, sino la salida de un AST o algo similar. Los compiladores tienen un proceso similar. La distinción no es tan clara como crees que es.
Robert Harvey
55
"Puede" liofilizar "la salida del JIT en un ejecutable binario real utilizando NGen.exe, cuyo producto habría sido el resultado de un compilador en los viejos tiempos": Pero aún hoy es el resultado de un compilador (es decir, el compilador justo a tiempo). No importa cuándo se ejecuta el compilador, sino lo que hace. Un compilador toma como entrada una representación de un fragmento de código y genera una nueva representación. Un intérprete generará el resultado de ejecutar ese fragmento de código. Estos son dos procesos diferentes, sin importar cómo los mezcle y cuándo ejecute qué.
Giorgio
44
"Compilador" es simplemente el término que han elegido adjuntar a GCC. Eligieron no llamar a NGen un compilador, a pesar de que produce código de máquina, prefiriendo en cambio adjuntar ese término al paso anterior, que podría decirse que es un intérprete, a pesar de que produce código de máquina (algunos intérpretes también lo hacen). Mi punto es que hoy en día no existe un principio vinculante que pueda invocar para llamar definitivamente a algo compilador o intérprete, aparte de "así es como siempre lo han llamado".
Robert Harvey
44
Según mi comprensión muy limitada, en estos días las CPU x86 están a medio camino de ser motores JIT basados ​​en hardware de todos modos, con el ensamblaje teniendo una relación cada vez más tenue con lo que se ejecuta exactamente.
Leushenko
44
@RobertHarvey, aunque estoy de acuerdo en que no hay una línea divisoria clara entre las técnicas utilizadas en un intérprete y un compilador, hay una división bastante clara en la función: si el resultado de ejecutar una herramienta determinada con el código de un programa como entrada es la ejecución de esa programa, la herramienta es un intérprete. Si el resultado es la salida de una traducción del programa a una forma menos abstracta, es un compilador. Si el resultado es la traducción a una forma más abstracta, se trata de un descompilador. Sin embargo, los casos en que más de uno de estos resultados son ambiguos.
Jules
34

El resumen que doy a continuación se basa en "Compiladores, principios, técnicas y herramientas", Aho, Lam, Sethi, Ullman, (Pearson International Edition, 2007), páginas 1, 2, con la adición de algunas ideas propias.

Los dos mecanismos básicos para procesar un programa son la compilación y la interpretación .

La compilación toma como entrada un programa fuente en un idioma dado y genera un programa objetivo en un idioma objetivo.

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

Si el idioma de destino es el código de máquina, se puede ejecutar directamente en algún procesador:

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

La compilación implica escanear y traducir todo el programa de entrada (o módulo) y no implica ejecutarlo.

La interpretación toma como entrada el programa fuente y su entrada, y produce la salida del programa fuente.

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

La interpretación generalmente implica procesar (analizar y ejecutar) el programa una declaración a la vez.

En la práctica, muchos procesadores de lenguaje usan una combinación de los dos enfoques. Por ejemplo, los programas Java se traducen primero (compilan) en un programa intermedio (código de bytes):

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

La salida de este paso es ejecutada (interpretada) por una máquina virtual:

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

Para complicar aún más las cosas, la JVM puede realizar una compilación justo a tiempo en tiempo de ejecución para convertir el código de bytes a otro formato, que luego se ejecuta.

Además, incluso cuando compila en lenguaje máquina, hay un intérprete que ejecuta su archivo binario que implementa el procesador subyacente. Por lo tanto, incluso en este caso está utilizando un híbrido de compilación + interpretación.

Por lo tanto, los sistemas reales usan una combinación de ambos, por lo que es difícil decir si un procesador de lenguaje dado es un compilador o un intérprete, porque probablemente usará ambos mecanismos en diferentes etapas de su procesamiento. En este caso, probablemente sería más apropiado usar otro término más neutral.

Sin embargo, la compilación y la interpretación son dos tipos distintos de procesamiento, como se describe en los diagramas anteriores,

Para responder las preguntas iniciales.

¿Un compilador crearía un lenguaje de máquina que se ejecuta directamente en el hardware físico?

No necesariamente, un compilador traduce un programa escrito para una máquina M1 a un programa equivalente escrito para una máquina M2. La máquina de destino puede implementarse en hardware o ser una máquina virtual. Conceptualmente no hay diferencia. El punto importante es que un compilador mira un fragmento de código y lo traduce a otro idioma sin ejecutarlo.

Entonces, ¿un intérprete no produce lenguaje de máquina pero un compilador lo hace para su entrada?

Si al producir se está refiriendo a la salida, un compilador produce un programa de destino que puede estar en lenguaje de máquina, un intérprete no.

Giorgio
fuente
77
En otras palabras: un intérprete toma un programa P y produce su salida O, un compilador toma P y produce un programa P 'que produce O; los intérpretes a menudo incluyen componentes que son compiladores (p. ej., a un código de bytes, una representación intermedia o instrucciones de máquina JIT) y del mismo modo un compilador puede incluir un intérprete (p. ej., para evaluar los cálculos en tiempo de compilación).
Jon Purdy
"un compilador puede incluir un intérprete (por ejemplo, para evaluar los cálculos en tiempo de compilación)": Buen punto. Supongo que las macros de Lisp y las plantillas de C ++ podrían procesarse previamente de esta manera.
Giorgio
Aún más simple, el preprocesador C compila el código fuente C con directivas CPP en C simple e incluye un intérprete para expresiones booleanas como defined A && !defined B.
Jon Purdy
@JonPurdy Estoy de acuerdo con eso, pero también agregaría una clase, "intérpretes tradicionales", que no hacen uso de representaciones intermedias más allá de quizás una versión simbólica de la fuente. Los ejemplos serían shells, muchos BASICs, Lisp clásico, Tcl anterior a 8.0 y bc.
hobbs
1
@naxa: vea la respuesta de Lawrence y los comentarios de Paul Draper sobre los tipos de compilador. Un ensamblador es un tipo especial de compilador donde (1) el lenguaje de salida está destinado a la ejecución directa por una máquina o máquina virtual y (2) existe una correspondencia uno a uno muy simple entre las instrucciones de entrada y las instrucciones de salida.
Jules
22

Un compilador crearía lenguaje de máquina

No. Un compilador es simplemente un programa que toma como entrada un programa escrito en un lenguaje A y produce como salida un programa semánticamente equivalente en el lenguaje B . El lenguaje B puede ser cualquier cosa, no tiene que ser lenguaje de máquina.

Un compilador puede compilar desde un lenguaje de alto nivel a otro lenguaje de alto nivel (por ejemplo, GWT, que compila Java a ECMAScript), desde un lenguaje de alto nivel a un lenguaje de bajo nivel (por ejemplo, Gambit, que compila Scheme a C), de un lenguaje de alto nivel a código de máquina (por ejemplo, GCJ, que compila Java a código nativo), de un lenguaje de bajo nivel a un lenguaje de alto nivel (por ejemplo, Clue, que compila C a Java, Lua, Perl, ECMAScript y Common Lisp), de un lenguaje de bajo nivel a otro lenguaje de bajo nivel (por ejemplo, el SDK de Android, que compila el código de bytes JVML al código de bytes Dalvik), de un lenguaje de bajo nivel a código de máquina (por ejemplo, el compilador C1X que es parte de HotSpot, que compila el código de bytes JVML al código de máquina), el código de máquina a un lenguaje de alto nivel (cualquier llamado "descompilador", también Emscripten, que compila el código de máquina LLVM a ECMAScript),código de máquina a lenguaje de bajo nivel (por ejemplo, el compilador JIT en JPC, que compila código nativo x86 a código de bytes JVML) y código nativo a código nativo (por ejemplo, el compilador JIT en PearPC, que compila código nativo PowerPC a código nativo x86).

Tenga en cuenta también que "código de máquina" es un término muy difuso por varias razones. Por ejemplo, hay CPU que ejecutan de forma nativa el código de bytes JVM, y hay intérpretes de software para el código de máquina x86. Entonces, ¿qué hace que un "código máquina nativo" pero no el otro? Además, cada idioma es código para una máquina abstracta para ese idioma.

Hay muchos nombres especializados para compiladores que realizan funciones especiales. A pesar del hecho de que estos son nombres especializados, todos estos todavía son compiladores, solo tipos especiales de compiladores:

  • si se percibe que el lenguaje A tiene aproximadamente el mismo nivel de abstracción que el lenguaje B , el compilador podría llamarse un transpilador (por ejemplo, un transpilador Ruby-to-ECMAScript o un transpiler ECMAScript2015-to-ECMAScript5)
  • Si se percibe que el lenguaje A está en un nivel de abstracción de nivel más bajo que el lenguaje B , el compilador podría llamarse un descompilador (por ejemplo, un descompilador x86-código-máquina-a-C)
  • si el idioma A == idioma B , el compilador podría llamarse optimizador , ofuscador o minificador (dependiendo de la función particular del compilador)

que se ejecuta en el hardware físico directamente?

No necesariamente. Se puede ejecutar en un intérprete o en una máquina virtual. Podría compilarse en un idioma diferente.

Entonces, ¿un intérprete no produce lenguaje de máquina pero un compilador lo hace para su entrada?

Un intérprete no produce nada. Simplemente ejecuta el programa.

Un compilador produce algo, pero no necesariamente tiene que ser lenguaje de máquina, puede ser cualquier lenguaje. ¡Incluso puede ser el mismo idioma que el idioma de entrada! Por ejemplo, Supercompilers, LLC tiene un compilador que toma Java como entrada y produce Java optimizado como salida. Hay muchos compiladores de ECMAScript que toman ECMAScript como sus entradas y producen ECMAScript optimizado, minimizado y ofuscado como su salida.


Usted también podría estar interesado en:

Jörg W Mittag
fuente
16

Creo que deberías descartar por completo la noción de "compilador versus intérprete", porque es una falsa dicotomía.

  • Un compilador es un transformador : transforma un programa de computadora escrito en un idioma de origen y genera un equivalente en un idioma de destino . Por lo general, el idioma de origen es de nivel superior que el idioma de destino, y si es al revés, a menudo llamamos a ese tipo de transformador un descompilador .
  • Un intérprete es un motor de ejecución . Ejecuta un programa de computadora escrito en un idioma, de acuerdo con las especificaciones de ese idioma. Usamos principalmente el término para software (pero en cierto modo, una CPU clásica se puede ver como un "intérprete" basado en hardware para su código de máquina).

La palabra colectiva para hacer que un lenguaje de programación abstracto sea útil en el mundo real es implementación .

En el pasado, la implementación de un lenguaje de programación a menudo consistía solo en un compilador (y la CPU para la que generó el código) o simplemente un intérprete, por lo que puede parecer que estos dos tipos de herramientas son mutuamente excluyentes. Hoy, puedes ver claramente que este no es el caso (y nunca fue para empezar). Adoptar una implementación sofisticada de lenguaje de programación e intentar introducir el nombre de "compilador" o "intérprete", a menudo lo llevará a resultados inconclusos o inconsistentes.

Una implementación de lenguaje de programación único puede involucrar cualquier número de compiladores e intérpretes , a menudo en múltiples formas (independientes, sobre la marcha), cualquier cantidad de otras herramientas, como analizadores y optimizadores estáticos , y cualquier cantidad de pasos. Incluso puede incluir implementaciones completas de cualquier cantidad de idiomas intermedios (que pueden no estar relacionados con el que se está implementando).

Los ejemplos de esquemas de implementación incluyen:

  • Compilador de CA que transforma C a código de máquina x86 y una CPU x86 que ejecuta ese código.
  • Compilador de CA que transforma C a LLVM IR, un compilador de back-end LLVM que transforma LLVM IR a código de máquina x86 y una CPU x86 que ejecuta ese código.
  • Compilador de CA que transforma C a LLVM IR y un intérprete de LLVM que ejecuta LLVM IR.
  • Un compilador de Java que transforma Java a bytecode JVM, y un JRE con un intérprete que ejecuta ese código.
  • Un compilador de Java que transforma Java a JVM bytecode, y un JRE con un intérprete que ejecuta algunas partes de ese código y un compilador que transforma otras partes de ese código en código de máquina x86, y una CPU x86 que ejecuta ese código.
  • Un compilador de Java que transforma Java a bytecode JVM, y una CPU ARM que ejecuta ese código.
  • AC # compilador que transforma C # a CIL, un CLR con un compilador que transforma CIL a código de máquina x86 y una CPU x86 que ejecuta ese código.
  • Un intérprete de Ruby que ejecuta Ruby.
  • Un entorno Ruby con un intérprete que ejecuta Ruby y un compilador que transforma Ruby a código máquina x86, y una CPU x86 que ejecuta ese código.

...y así.

Theodoros Chatzigiannakis
fuente
+1 para señalar que incluso las codificaciones que se diseñaron para la representación intermedia (por ejemplo, java bytecode) pueden tener implementaciones de hardware.
Jules
7

Si bien las líneas entre los compiladores y los intérpretes se han vuelto borrosas con el tiempo, aún se puede trazar una línea entre ellas al observar la semántica de lo que debe hacer el programa y lo que hace el compilador / intérprete.

Un compilador generará otro programa (generalmente en un lenguaje de nivel inferior como el código de máquina) que, si ese programa se ejecuta, hará lo que su programa debería hacer.

Un intérprete hará lo que su programa debería hacer.

Con estas definiciones, los lugares donde se vuelve borroso son los casos en que se puede pensar que su compilador / intérprete hace cosas diferentes dependiendo de cómo lo mire. Por ejemplo, Python toma su código de Python y lo compila en un bytecode de Python compilado. Si este código de bytes de Python se ejecuta a través de un intérprete de código de bytes de Python , hace lo que su programa debía hacer. Sin embargo, en la mayoría de las situaciones, los desarrolladores de Python piensan que ambos pasos se realizan en un gran paso, por lo que optan por pensar que el intérprete de CPython interpreta su código fuente, y el hecho de que se compiló en el camino se considera un detalle de implementación . De esta manera, todo es cuestión de perspectiva.

Cort Ammon
fuente
5

Aquí hay una simple desambiguación conceptual entre compiladores e intérpretes.

Considere 3 idiomas: lenguaje de programación , P (en qué está escrito el programa); lenguaje de dominio , D (para lo que sucede con el programa en ejecución); y idioma de destino , T (algún tercer idioma).

Conceptualmente

  • un compilador traduce P a T para que pueda evaluar T (D); mientras

  • Un intérprete evalúa P (D) directamente.

Lawrence
fuente
1
La mayoría de los intérpretes modernos no evalúan el idioma fuente directamente, sino una representación intermedia del idioma fuente.
Robert Harvey
44
@RobertHarvey Eso no cambia la distinción conceptual entre los términos.
Lawrence
1
Entonces, a lo que realmente se refiere como intérprete es a la parte que evalúa la representación intermedia. La parte que crea la representación intermedia es un compilador , por su definición.
Robert Harvey
66
@RobertHarvey No realmente. Los términos dependen del nivel de abstracción en el que esté trabajando. Si miras debajo, la herramienta podría estar haciendo cualquier cosa. Por analogía, digamos que vas a un país extranjero y traes a un amigo bilingüe Bob. Si te comunicas con los lugareños hablando con Bob, quien a su vez habla con los lugareños, Bob actúa como intérprete para ti (incluso si garabatea en su idioma antes de hablar). Si le pide frases a Bob y Bob las escribe en el idioma extranjero, y se comunica con los locales refiriéndose a esos escritos (no a Bob), Bob actúa como un compilador para usted.
Lawrence
1
Excelente respuesta Vale la pena señalar: hoy en día puede escuchar "transpiler". Ese es un compilador donde P y T son niveles similares de abstracción, para alguna definición de similar. (Por ejemplo, un transpilador ES5 a ES6.)
Paul Draper