解释器会产生机器代码吗?


42

我深入研究了编译器和解释器的主题。我想检查一下我的基本理解是否正确,所以让我们假设以下内容:

我有一种叫做“ Foobish”的语言,其关键字是

<OUTPUT> 'TEXT', <Number_of_Repeats>;

因此,如果我想在控制台上打印10次,我会写

OUTPUT 'Hello World', 10;

Hello World.foobish文件。

现在,我用自己选择的语言(在这种情况下为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);
            }
        }
    }
}

在非常简单的解释程序级别,解释程序将分析脚本文件等,并以解释程序的实现方式执行愚蠢的语言。

编译器会创建直接在物理硬件上运行的机器语言吗?

因此,解释器不会产生机器语言,但是编译器会为输入提供机器语言吗?

在编译器和解释器的基本工作方式上是否存在误解?


21
您认为C#“编译器”做什么?作为提示,它不会产生机器代码。
菲利普·肯德尔2015年

3
Java编译器为JVM生成代码。因此,编译器的目标计算机可以是不直接由硬件执行的虚拟机。解释器和编译器之间的主要区别在于,编译器首先检查整个源代码并将其翻译为目标机器语言。然后,该编译后的代码将由预定的机器执行。另一方面,解释器将即时翻译和执行您的程序块。
乔治

@乔治:您的意思是说,像准时制吗?
罗伯特·哈维

2
@RobertHarvey:我的意思是Java编译器(javac):据我所知,它为JVM生成字节码。而且,再次是AFAIK,稍后的JIT(在运行时)将一些经常使用的字节码编译为本地机器语言。
乔治

4
编译器意味着翻译。它可以发出各种语言:c,汇编语言,javascript,机器代码。
Esben Skov Pedersen

Answers:


77

术语“解释器”和“编译器”比以前模糊得多。许多年前,编译器生成稍后执行的机器代码更为普遍,而解释器则或多或少地直接“执行”了源代码。因此,这两个术语在那时就已广为人知。

但是今天,“编译器”和“解释器”的使用有很多变体。例如,VB6“编译”为字节码(一种中间语言),然后由VB运行时“解释”。在C#中发生了类似的过程,该过程产生CIL,然后由即时编译器(JIT)执行该CIL,在过去,JIT曾被认为是解释器。您可以使用NGen.exe将JIT的输出“冻结”为实际的二进制可执行文件,该文件的产物是过去编译器的结果。

因此,您所提问题的答案并不像以前那样简单。

Wikipedia上的进阶阅读
编译器与口译员


6
@乔治:如今,大多数解释器实际上并不执行源代码,而是执行AST或类似内容的输出。编译器具有类似的过程。这种区别并不像您想象的那么清晰。
罗伯特·哈维

5
“您可以使用NGen.exe将JIT的输出“冻结”为实际的二进制可执行文件,其产物在过去是编译器的结果。”:但是今天仍然是结果编译器(即即时编译器)。何时运行编译器并不重要,但是它会做什么。编译器将一段代码的表示作为输入,然后输出新的表示。解释器将输出执行该代码段的结果。无论您如何混合它们以及执行什么操作,这都是两个不同的过程。
乔治

4
“编译器”只是他们选择附加到GCC的术语。他们选择不将NGen称为编译器,即使它会生成机器代码,而是更愿意将该术语附加到上一步,尽管可以生成机器代码,但可以将其称为解释器(某些解释器也可以这样做)。我的观点是,如今除了“那是他们一直称呼的”之外,没有可用于定义地调用编译器或解释器的绑定原理。
罗伯特·哈维

4
以我非常有限的理解,如今,无论如何,x86 CPU都已经成为基于硬件的JIT引擎的一半,而程序集与确切执行的内容之间的关系却日趋衰落。
Leushenko

4
@RobertHarvey虽然我同意解释器和编译器中使用的技术之间没有明确的分界线,但功能上却存在相当明确的分界:如果以程序代码作为输入执行给定工具的结果是该程序的执行程序,该工具是解释器。如果结果是将程序翻译成不太抽象的形式的输出,则它是编译器。如果结果是转换为更抽象的形式,则为反编译器。但是,其中多个结果之一不明确的情况。
2015年

34

我在下面给出的摘要基于“编译器,原理,技术和工具”,Aho,Lam,Sethi,Ullman,(Pearson国际版,2007年),第1、2页,以及我自己的一些想法。

处理程序的两个基本机制是编译解释

编译将给定语言的源程序作为输入,并以目标语言输出目标程序。

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

如果目标语言是机器代码,则可以直接在某些处理器上执行:

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

编译涉及扫描和翻译整个输入程序(或模块),而不涉及执行它。

解释将源程序及其输入作为输入,并产生源程序的输出

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

解释通常涉及一次处理(分析和执行)程序一个语句。

在实践中,许多语言处理器将两种方法结合使用。例如,首先将Java程序翻译(编译)成中间程序(字节码):

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

然后由虚拟机执行(解释)此步骤的输出:

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

为了使事情更加复杂,JVM可以在运行时执行即时编译,以将字节码转换为另一种格式,然后执行该格式。

同样,即使您编译为机器语言,也有一个解释器运行您的二进制文件,该文件由基础处理器实现。因此,即使在这种情况下,您也使用混合编译+解释。

因此,实际系统使用了两者的混合,因此很难说给定的语言处理器是编译器还是解释器,因为它可能会在处理的不同阶段使用这两种机制。在这种情况下,使用另一个更中立的术语可能更合适。

不过,如上图所示,编译和解释是两种不同的处理方式,

回答最初的问题。

编译器会创建直接在物理硬件上运行的机器语言吗?

编译器不必将为机器M1编写的程序转换为为机器M2编写的等效程序。目标计算机可以用硬件实现,也可以是虚拟机。从概念上讲没有区别。重要的一点是,编译器查看一段代码,然后将其翻译为另一种语言,而不执行该代码。

因此,解释器不会产生机器语言,但是编译器会为输入提供机器语言吗?

如果通过生成来引用输出,则编译器生成的目标程序可能是机器语言,而解释程序则不会。


7
换句话说:解释器获取程序P并生成其输出O,编译器获取P并生成输出O的程序P'。解释器通常包括作为编译器的组件(例如,用于字节码,中间表示或JIT机器指令),并且类似地,编译器可以包括解释器(例如,用于评估编译时计算)。
乔恩·普迪

“编译器可以包括解释器(例如,用于评估编译时计算)”:重点。我想Lisp宏和C ++模板可能会以这种方式进行预处理。
乔治

C预处理器甚至更简单,它使用CPP指令将C源代码编译为纯C,并包含一个布尔表达式(如)的解释器defined A && !defined B
乔恩·普迪

@JonPurdy我同意这一点,但是我还要添加一个类“传统解释器”,该类不使用可能超出源标记形式的中间表示形式。例如shell,许多BASIC,经典的Lisp,8.0之前的Tcl和bc。
霍布斯2015年

1
@naxa-参见Lawrence的答案和Paul Draper对编译器类型的评论。汇编器是一种特殊的编译器,其中(1)输出语言旨在由机器或虚拟机直接执行,并且(2)输入语句和输出指令之间存在非常简单的一对一对应关系。
2015年

22

编译器将创建机器语言

不会。编译器只是一个程序,将以语言A编写的程序作为输入,并以语言B生成语义上等效的程序作为输出。语言B可以是任何东西,不一定非要是机器语言。

编译器可以从高级语言编译为另一种高级语言(例如,将Java编译为ECMAScript的GWT),也可以从高级语言编译为低级语言(例如,将Scheme编译为C的Gambit),从高级语言到机器代码(例如,将Java编译为本地代码的GCJ),从低级语言到高级语言(例如,将C编译为Java,Lua,Perl,ECMAScript和Common的线索) Lisp),从低级语言到另一种低级语言(例如,将JVML字节码编译成Dalvik字节码的Android SDK),从低级语言到机器码(例如,属于HotSpot的C1X编译器,将JVML字节码编译为机器代码),将机器代码编译为高级语言(任何所谓的“反编译器”,也称为Emscripten,它将LLVM机器代码编译为ECMAScript),机器代码转换为低级语言(例如,JPC中的JIT编译器,将x86本机代码编译为JVML字节码),以及原始代码(例如PearPC中的JIT编译器,将PowerPC本机代码编译为x86本机代码)。

还请注意,由于以下几个原因,“机器代码”是一个非常模糊的术语。例如,有一些CPU本机执行JVM字节代码,还有一些x86机器代码的软件解释器。那么,是什么使一个“本机代码”而不是另一个呢?同样,每种语言都是该语言的抽象机的代码。

对于执行特殊功能的编译器,有许多专门的名称。尽管这些是专用名称,但所有这些仍然都是编译器,只是特殊类型的编译器:

  • 如果语言被感知为在大致抽象作为语言的同一水平,编译器可以被称为transpiler(例如一个Ruby到ECMAScript的transpiler或ECMAScript2015到ECMAScript5-transpiler)
  • 如果认为语言A的抽象水平低于语言B的抽象水平,则可以将编译器称为反编译器(例如,x86机器代码到C的反编译器)
  • 如果语言A ==语言B,则编译器可能称为优化器混淆器缩小器(取决于编译器的特定功能)

哪个直接在物理硬件上运行?

不必要。它可以在解释器或VM中运行。可以将其进一步编译为其他语言。

因此,解释器不会产生机器语言,但是编译器会为输入提供机器语言吗?

口译员不会产生任何东西。它只是运行程序。

编译器可以产生某种东西,但不一定必须是机器语言,它可以是任何语言。它甚至可以是与输入语言相同的语言!例如,Supercompilers,LLC的编译器以Java作为输入,并生成优化的Java作为输出。有许多ECMAScript编译器将ECMAScript作为输入,并产生经过优化,最小化和混淆的ECMAScript作为输出。


您也可能对。。。有兴趣:


16

我认为您应该完全放弃“编译器解释器” 的概念,因为这是错误的二分法。

  • 编译器是一个变压器:它把写在一个计算机程序的源语言,并输出在等效目标语言。通常,源语言比目标语言更高级-如果相反,我们通常将这种转换器称为反编译器
  • 一个解释器是一个执行引擎。根据该语言的规范,它执行以一种语言编写的计算机程序。我们通常将术语用于软件(但在某种程度上,经典CPU可以看作是其机器代码的基于硬件的“解释器”)。

使抽象的编程语言在现实世界中有用的统称是实现

过去,编程语言实现通常仅由编译器(以及为其生成代码的CPU)或解释器组成-因此,看起来这两种工具是互斥的。今天,您可以清楚地看到事实并非如此(而且从来没有开始过)。采用复杂的编程语言实现,并尝试使用“编译器”或“解释器”这个名称,通常会导致结果不确定或不一致。

单一的编程语言实现可以涉及任意数量的编译器和解释器,通常以多种形式(独立,即时),任意数量的其他工具(如静态分析器优化器)以及任意数量的步骤。它甚至可以包括多种中间语言的完整实现(可能与所实现的一种无关)。

实施方案的示例包括:

  • 将C转换为x86机器代码的AC编译器,以及执行该代码的x86 CPU。
  • 将C转换为LLVM IR的AC编译器,将LLVM IR转换为x86机器代码的LLVM后端编译器,以及执行该代码的x86 CPU。
  • 将C转换为LLVM IR的AC编译器,以及执行LLVM IR的LLVM解释器。
  • 将Java转换为JVM字节码的Java编译器,以及带有执行该代码的解释器的JRE。
  • 将Java转换为JVM字节码的Java编译器,以及具有执行该代码的某些部分的解释器和将该代码的其他部分转换为x86机器代码的编译器,以及执行该代码的x86 CPU的JRE。
  • 将Java转换为JVM字节码的Java编译器,以及执行该代码的ARM CPU。
  • 将C#转换为CIL的AC#编译器,带有将CIL转换为x86机器代码的编译器的CLR和执行该代码的x86 CPU。
  • 一个执行Ruby的Ruby解释器。
  • 一个Ruby环境,具有一个执行Ruby的解释器和一个将Ruby转换为x86机器代码的编译器,以及一个执行该代码的x86 CPU。

...等等。


+1指出即使是为中间表示而设计的编码(例如Java字节码)也可以具有硬件实现。
Jules 2015年

7

尽管编译器和解释器之间的界限随着时间的推移变得越来越模糊,但是仍然可以通过查看程序应该做什么以及编译器/解释器做什么的语义来在它们之间划清界限。

编译器将生成另一个程序(通常使用较低级的语言,例如机器代码),如果该程序运行,它将执行您的程序应做的事情。

解释器将执行您的程序应执行的操作。

有了这些定义,模糊的地方就是您的编译器/解释器根据您的看待方式可以认为做不同的事情。例如,Python接收您的Python代码并将其编译为已编译的Python字节码。如果此Python字节码通过Python字节码解释器运行,则它将执行您的程序应该执行的操作。但是,在大多数情况下,Python开发人员认为这两个步骤都是一步完成的,因此他们选择将CPython解释器解释为解释其源代码,并且将其编译的事实视为实现细节。 。这样,这完全是透视问题。


5

这是编译器和解释器之间的简单概念上的歧义。

考虑3种语言:编程语言P(编写程序的语言);领域语言D(用于运行程序的内容);和目标语言T(一些第三种语言)。

从概念上讲

  • 一个编译器转换P至T,这样就可以评价T(d); 而

  • 一个解释器求值P(d)直接。


1
大多数现代口译员实际上并不直接评估源语言,而是对源语言进行某种中间表示。
罗伯特·哈维

4
@RobertHarvey这不会改变术语之间的概念区别。
劳伦斯

1
因此,您真正指的是解释器,它是评估中间表示的部分。根据您的定义,创建中间表示的部分是一个编译器
罗伯特·哈维

6
@RobertHarvey并非如此。这些术语取决于您正在使用的抽象级别。如果您在下面看,该工具可能正在做任何事情。打个比方,说您去异国,带一个双语朋友鲍勃。如果您通过与鲍勃交谈来与当地人交流,而鲍勃又与当地人交谈,则鲍勃将充当您的口译员(即使他在讲话前用他们的语言书写)。如果您向Bob询问短语,而Bob则用外语编写短语,并且您通过引用这些著作(而非Bob)与当地人进行交流,则Bob会为您充当编译器。
劳伦斯

1
极好的答案。值得注意的是:如今您可能会听到“翻译”。那是一个编译器,其中P和T是相似的抽象级别,用于类似的定义。(例如,ES5到ES6的编译器。)
Paul Draper,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.