Um intérprete produz código de máquina?


42

Estudo intensamente os tópicos de compiladores e intérpretes. Quero verificar se meu entendimento básico está correto, então vamos assumir o seguinte:

Eu tenho um idioma chamado "Foobish" e suas palavras-chave são

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Então, se eu quiser imprimir no console 10 vezes, escreveria

OUTPUT 'Hello World', 10;

Olá, arquivo World.foobish.

Agora, escrevo um intérprete no idioma de minha escolha - C # neste 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);
            }
        }
    }
}

Em um nível de intérprete muito fácil, o intérprete analisaria o arquivo de script etc. e executaria a linguagem foobish no caminho da implementação do intérprete.

Um compilador criaria uma linguagem de máquina que é executada diretamente no hardware físico?

Portanto, um intérprete não produz linguagem de máquina, mas um compilador faz isso por sua entrada?

Tenho algum mal-entendido da maneira básica como os compiladores e intérpretes funcionam?


21
O que você acha que o "compilador" C # faz? Como sugestão, ele não produz código de máquina.
Philip Kendall

3
Um compilador Java produz código para a JVM. Portanto, a máquina de destino de um compilador pode ser uma máquina virtual que não é executada diretamente pelo hardware. A principal diferença entre intérprete e compilador é que um compilador primeiro verifica e traduz todo o código-fonte em um idioma de máquina de destino. Esse código compilado é então executado pela máquina para a qual foi destinado. Por outro lado, um intérprete irá traduzir e executar trechos do seu programa em tempo real.
Giorgio

@Giorgio: Você quer dizer, como um JIT?
Robert Harvey

2
@RobertHarvey: Eu quis dizer o Java Compiler (javac): tanto quanto sei, produz bytecode para a JVM. E, novamente AFAIK, o JIT mais tarde (em tempo de execução) compila alguns bytecodes que são usados ​​com frequência na linguagem de máquina nativa.
Giorgio

4
um compilador significa traduzir. Ele pode emitir todos os tipos de linguagem: c, assembly, javascript, código de máquina.
Esben Skov Pedersen

Respostas:


77

Os termos "intérprete" e "compilador" são muito mais confusos do que costumavam ser. Muitos anos atrás, era mais comum os compiladores produzirem código de máquina a ser executado posteriormente, enquanto os intérpretes mais ou menos "executavam" o código-fonte diretamente. Então esses dois termos eram bem entendidos na época.

Mas hoje existem muitas variações no uso de "compilador" e "intérprete". Por exemplo, o VB6 "compila" o código de bytes (uma forma de linguagem intermediária ), que é então "interpretada" pelo VB Runtime. Um processo semelhante ocorre no C #, que produz o CIL que é então executado por um JIT (Just-In-Time Compiler) que, antigamente, era considerado um intérprete. Você pode "congelar" a saída do JIT em um executável binário real usando o NGen.exe , cujo produto teria sido o resultado de um compilador nos velhos tempos.

Portanto, a resposta para sua pergunta não é tão direta quanto era antes.

Leitura adicional
Compiladores vs. Intérpretes na Wikipedia


6
@Giorgio: Atualmente, a maioria dos intérpretes atualmente não executa o código fonte, mas a saída de um AST ou algo semelhante. Compiladores têm um processo semelhante. A distinção não é tão clara quanto você pensa.
Robert Harvey

5
"Você pode" congelar "a saída do JIT em um executável binário real usando o NGen.exe, cujo produto seria o resultado de um compilador nos velhos tempos.": Mas ainda hoje é o resultado de um compilador (ou seja, o compilador just-in-time). Não importa quando o compilador é executado, mas o que ele faz. Um compilador toma como entrada uma representação de um pedaço de código e gera uma nova representação. Um intérprete produzirá o resultado da execução desse trecho de código. Esses são dois processos diferentes, não importa como você os misture e quando você executa o que.
Giorgio

4
"Compilador" é simplesmente o termo que eles escolheram anexar ao GCC. Eles escolheram não chamar o NGen de compilador, mesmo que ele produza código de máquina, preferindo anexar esse termo à etapa anterior, que poderia ser chamada de intérprete, mesmo que produza código de máquina (alguns intérpretes também o fazem). Meu argumento é que hoje em dia não existe um princípio vinculativo que você possa invocar para chamar definitivamente algo de compilador ou intérprete, além de "é assim que eles sempre o chamavam".
Robert Harvey

4
De acordo com meu entendimento muito limitado, atualmente, as CPUs x86 estão na metade do caminho para serem mecanismos JIT baseados em hardware, com o conjunto tendo uma relação cada vez menor com o que exatamente é executado.
Leushenko 23/10/2015

4
@RobertHarvey, embora eu concorde que não há uma linha divisória clara entre as técnicas usadas em um intérprete e um compilador, há uma divisão bastante clara na função: se o resultado da execução de uma determinada ferramenta com o código de um programa como entrada é a execução dessa programa, a ferramenta é um intérprete. Se o resultado for a saída de uma tradução do programa para uma forma menos abstrata, é um compilador. Se o resultado for a tradução para uma forma mais abstrata, será um descompilador. Casos em que mais de um desses resultados são ambíguos, no entanto.
Jules

34

O resumo a seguir é baseado em "Compiladores, Princípios, Técnicas e Ferramentas", Aho, Lam, Sethi, Ullman, (Pearson International Edition, 2007), páginas 1, 2, com a adição de algumas idéias minhas.

Os dois mecanismos básicos para processar um programa são compilação e interpretação .

A compilação recebe como entrada um programa de origem em um determinado idioma e gera um programa de destino em um idioma de destino.

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

Se o idioma de destino for um código de máquina, ele poderá ser executado diretamente em algum processador:

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

A compilação envolve a varredura e tradução de todo o programa de entrada (ou módulo) e não envolve a execução.

A interpretação toma como entrada o programa de origem e sua entrada e produz a saída do programa de origem

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

A interpretação geralmente envolve o processamento (análise e execução) do programa, uma instrução de cada vez.

Na prática, muitos processadores de linguagem usam uma mistura das duas abordagens. Por exemplo, os programas Java são primeiro traduzidos (compilados) em um programa intermediário (código de bytes):

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

a saída desta etapa é então executada (interpretada) por uma máquina virtual:

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

Para complicar ainda mais, a JVM pode executar a compilação just-in-time no tempo de execução para converter o código de bytes em outro formato, que é então executado.

Além disso, mesmo quando você compila a linguagem de máquina, há um intérprete executando seu arquivo binário que é implementado pelo processador subjacente. Portanto, mesmo neste caso, você está usando um híbrido de compilação + interpretação.

Portanto, sistemas reais usam uma mistura dos dois, por isso é difícil dizer se um determinado processador de linguagem é um compilador ou um intérprete, porque provavelmente usará os dois mecanismos em diferentes estágios de seu processamento. Nesse caso, provavelmente seria mais apropriado usar outro termo mais neutro.

No entanto, compilação e interpretação são dois tipos distintos de processamento, conforme descrito nos diagramas acima,

Para responder às perguntas iniciais.

Um compilador criaria uma linguagem de máquina que é executada diretamente no hardware físico?

Não necessariamente, um compilador converte um programa gravado para uma máquina M1 em um programa equivalente gravado para uma máquina M2. A máquina de destino pode ser implementada em hardware ou ser uma máquina virtual. Conceitualmente, não há diferença. O ponto importante é que um compilador analisa um pedaço de código e o traduz para outro idioma sem executá-lo.

Portanto, um intérprete não produz linguagem de máquina, mas um compilador faz isso por sua entrada?

Se, ao produzir uma referência à saída, um compilador produz um programa de destino que pode estar na linguagem de máquina, um intérprete não.


7
Em outras palavras: um intérprete pega um programa P e produz sua saída O, um compilador pega P e produz um programa P 'que gera O; os intérpretes geralmente incluem componentes que são compiladores (por exemplo, para um bytecode, uma representação intermediária ou instruções de máquina JIT) e da mesma forma um compilador pode incluir um intérprete (por exemplo, para avaliar cálculos em tempo de compilação).
Jon Purdy

"um compilador pode incluir um intérprete (por exemplo, para avaliar cálculos em tempo de compilação)": Bom argumento. Eu acho que macros Lisp e modelos C ++ podem ser pré-processados ​​dessa maneira.
Giorgio

Ainda mais simples, o pré-processador C compila o código fonte C com diretivas CPP em C simples e inclui um intérprete para expressões booleanas como defined A && !defined B.
Jon Purdy

@ JonPurdy, eu concordaria com isso, mas também acrescentaria uma classe, "intérpretes tradicionais", que não faz uso de representações intermediárias além de uma versão tokenizada da fonte. Exemplos seriam conchas, muitos BASICs, Lisp clássico, Tcl anterior a 8.0 e bc.
Hbbs #

1
@naxa - veja a resposta de Lawrence e os comentários de Paul Draper sobre os tipos de compilador. Um assembler é um tipo especial de compilador em que (1) a linguagem de saída se destina à execução direta por uma máquina ou máquina virtual e (2) existe uma correspondência individual muito simples entre instruções de entrada e instruções de saída.
Jules

22

Um compilador criaria linguagem de máquina

No. Um compilador é simplesmente um programa que toma como entrada de um programa escrito numa linguagem Um e produz como saída um programa semanticamente equivalentes em linguagem B . A linguagem B pode ser qualquer coisa, não precisa ser uma linguagem de máquina.

Um compilador pode compilar de uma linguagem de alto nível para outra linguagem de alto nível (por exemplo, GWT, que compila Java para ECMAScript), de uma linguagem de alto nível para uma linguagem de baixo nível (por exemplo, Gambit, que compila Esquema para C), de uma linguagem de alto nível para código de máquina (por exemplo, GCJ, que compila Java para código nativo), de uma linguagem de baixo nível para uma linguagem de alto nível (por exemplo, Clue, que compila C para Java, Lua, Perl, ECMAScript e Common Lisp), de um idioma de baixo nível para outro idioma de baixo nível (por exemplo, o Android SDK, que compila bytecode JVML para Dalvik bytecode), de um idioma de baixo nível para código de máquina (por exemplo, o compilador C1X que faz parte do HotSpot, que compila bytecode JVML para código de máquina), código de máquina para uma linguagem de alto nível (qualquer "descompilador", também Emscripten, que compila código de máquina LLVM para ECMAScript),código de máquina para linguagem de baixo nível (por exemplo, o compilador JIT no JPC, que compila o código nativo x86 no JVML bytecode) e código nativo para o código nativo (por exemplo, o compilador JIT no PearPC, que compila o código nativo do PowerPC para o código nativo x86).

Observe também que "código de máquina" é realmente um termo confuso por vários motivos. Por exemplo, existem CPUs que executam nativamente o código de bytes da JVM e existem intérpretes de software para o código de máquina x86. Então, o que faz um "código de máquina nativo", mas não o outro? Além disso, todo idioma é código para uma máquina abstrata para esse idioma.

Existem muitos nomes especializados para compiladores que executam funções especiais. Apesar de serem nomes especializados, todos ainda são compiladores, apenas tipos especiais de compiladores:

  • se a linguagem A é percebida como aproximadamente o mesmo nível de abstração da linguagem B , o compilador pode ser chamado de transpiler (por exemplo, um transpiler Ruby para ECMAScript ou um transpiler ECMAScript2015 para ECMAScript5)
  • se a linguagem A é percebida como um nível de abstração de nível inferior ao idioma B , o compilador pode ser chamado de descompilador (por exemplo, um descompilador x86-código-de-máquina-para-C-descompilador)
  • se o idioma A == idioma B , o compilador pode ser chamado de otimizador , ofuscador ou minificador (dependendo da função específica do compilador)

que roda diretamente no hardware físico?

Não necessariamente. Pode ser executado em um intérprete ou em uma VM. Poderia ser compilado para outro idioma.

Portanto, um intérprete não produz linguagem de máquina, mas um compilador faz isso por sua entrada?

Um intérprete não produz nada. Apenas executa o programa.

Um compilador produz algo, mas não precisa necessariamente ser linguagem de máquina, pode ser qualquer idioma. Pode até ser o mesmo idioma que o idioma de entrada! Por exemplo, Supercompilers, LLC possui um compilador que usa Java como entrada e produz Java otimizado como saída. Existem muitos compiladores ECMAScript que aceitam ECMAScript como entradas e produzem ECMAScript otimizado, minimizado e ofuscado como saída.


Você também pode estar interessado em:


16

Eu acho que você deve abandonar completamente a noção de "compilador versus intérprete", porque é uma dicotomia falsa.

  • Um compilador é um transformador : transforma um programa de computador escrito em um idioma de origem e gera um equivalente em um idioma de destino . Geralmente, o idioma de origem é de nível superior ao idioma de destino - e se for o contrário, geralmente chamamos esse tipo de transformador de descompilador .
  • Um intérprete é um mecanismo de execução . Ele executa um programa de computador escrito em um idioma, de acordo com a especificação desse idioma. Usamos principalmente o termo para software (mas, de certa forma, uma CPU clássica pode ser vista como um "intérprete" baseado em hardware para seu código de máquina).

A palavra coletiva para tornar útil uma linguagem de programação abstrata no mundo real é implementação .

No passado, uma implementação de linguagem de programação geralmente consistia em apenas um compilador (e a CPU para a qual gerou o código) ou apenas um intérprete - portanto, pode parecer que esses dois tipos de ferramentas são mutuamente exclusivos. Hoje, você pode ver claramente que esse não é o caso (e nunca foi assim). Tomar uma implementação sofisticada de linguagem de programação e tentar empurrar o nome "compilador" ou "intérprete" para ele geralmente leva a resultados inconclusivos ou inconsistentes.

Uma única implementação de linguagem de programação pode envolver qualquer número de compiladores e intérpretes , geralmente de várias formas (autônomo, em movimento), qualquer número de outras ferramentas, como analisadores e otimizadores estáticos , e várias etapas. Pode até incluir implementações inteiras de qualquer número de idiomas intermediários (que podem não estar relacionados ao que está sendo implementado).

Exemplos de esquemas de implementação incluem:

  • Compilador de CA que transforma C em código de máquina x86 e uma CPU x86 que executa esse código.
  • Compilador AC que transforma C em LLVM IR, um compilador back-end LLVM que transforma LLVM IR em código de máquina x86 e uma CPU x86 que executa esse código.
  • Compilador AC que transforma C em LLVM IR e um intérprete LLVM que executa LLVM IR.
  • Um compilador Java que transforma Java em bytecode da JVM e um JRE com um intérprete que executa esse código.
  • Um compilador Java que transforma Java em bytecode da JVM e um JRE com um intérprete que executa algumas partes desse código e um compilador que transforma outras partes desse código em código de máquina x86 e uma CPU x86 que executa esse código.
  • Um compilador Java que transforma Java em bytecode da JVM e uma CPU ARM que executa esse código.
  • Compilador AC # que transforma C # em CIL, um CLR com um compilador que transforma CIL em código de máquina x86 e uma CPU x86 que executa esse código.
  • Um intérprete Ruby que executa Ruby.
  • Um ambiente Ruby com um intérprete que executa Ruby e um compilador que transforma Ruby em código de máquina x86 e uma CPU x86 que executa esse código.

...e assim por diante.


+1 por apontar que mesmo as codificações projetadas para representação intermediária (por exemplo, java bytecode) podem ter implementações de hardware.
Jules

7

Embora as linhas entre compiladores e intérpretes tenham se tornado confusas ao longo do tempo, ainda é possível traçar uma linha entre eles, observando a semântica do que o programa deve fazer e o que o compilador / intérprete faz.

Um compilador irá gerar outro programa (normalmente em uma linguagem de nível inferior, como código de máquina) que, se esse programa for executado, fará o que seu programa deve fazer.

Um intérprete fará o que seu programa deve fazer.

Com essas definições, os locais onde fica confuso são os casos em que seu compilador / intérprete pode fazer coisas diferentes, dependendo de como você o vê. Por exemplo, o Python pega seu código Python e o compila em um bytecode Python compilado. Se esse bytecode do Python for executado através de um interpretador de bytecode do Python , ele fará o que seu programa deveria fazer. Na maioria das situações, no entanto, os desenvolvedores de Python pensam que essas duas etapas estão sendo executadas em uma grande etapa; portanto, optam por pensar no interpretador CPython como interpretando seu código-fonte, e o fato de ele ter sido compilado ao longo do caminho é considerado um detalhe da implementação. . Desta forma, é tudo uma questão de perspectiva.


5

Aqui está uma simples desambiguação conceitual entre compiladores e intérpretes.

Considere três idiomas: linguagem de programação , P (em que o programa está escrito); linguagem de domínio , D (para o que se passa com o programa em execução); e idioma alvo , T (algum terceiro idioma).

Conceitualmente,

  • um compilador converte P em T para que você possa avaliar T (D); enquanto que

  • um intérprete avalia diretamente P (D).


1
A maioria dos intérpretes modernos não avalia diretamente o idioma de origem, mas sim uma representação intermediária do idioma de origem.
Robert Harvey

4
@RobertHarvey Isso não muda a distinção conceitual entre os termos.
Lawrence

1
Então, o que você realmente se refere como intérprete é a parte que avalia a representação intermediária. A parte que cria a representação intermediária é um compilador , por sua definição.
Robert Harvey

6
@RobertHarvey Na verdade não. Os termos dependem do nível de abstração em que você está trabalhando. Se você olhar por baixo, a ferramenta pode estar fazendo qualquer coisa. Por analogia, digamos que você vá para um país estrangeiro e traga um amigo bilíngue Bob. Se você se comunicar com os habitantes locais conversando com Bob, que por sua vez fala com os habitantes locais, Bob atua como intérprete para você (mesmo que ele rabisque no idioma deles antes de falar). Se você pedir frases a Bob e Bob as escrever no idioma estrangeiro, e você se comunicar com os locais, consultando esses escritos (não Bob), Bob atuará como um compilador para você.
Lawrence

1
Excelente resposta. Vale notar: hoje em dia você pode ouvir "transpiler". Esse é um compilador em que P e T são níveis semelhantes de abstração, para alguma definição de similar. (Por exemplo, um transpiler ES5 para ES6).
Paul Draper
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.