实现编译器和解释器有啥区别?

Posted

技术标签:

【中文标题】实现编译器和解释器有啥区别?【英文标题】:What is the difference between implementing a compiler and an interpreter?实现编译器和解释器有什么区别? 【发布时间】:2010-10-03 06:40:45 【问题描述】:

我最近阅读了整本《龙之书》(只是为了好玩,我并没有真正打算实现一个真正的编译器),而我脑子里一直悬着这个大问题。

实现编译器和解释器有什么不同?

对我来说,编译器由以下部分组成:

词法分析器 解析器(构建语法树) 生成中间码(如三地址码) 如果你愿意,可以做所有这些疯狂的事情来优化:-) 从 3 地址码生成“程序集”或“本机代码”。

现在,很明显,解释器也具有与编译器相同的词法分析器和解析器。 但是之后它会做什么呢?

它是否“读取”语法树并直接执行它? (有点像有一个指向树中当前节点的指令指针,执行是一个大树遍历加上调用堆栈的内存管理)(如果是这样,它是如何做到的?我希望执行比检查它是什么类型的节点的巨大 switch 语句更好)

它会生成 3 个地址代码并对其进行解释吗? (如果是这样,它是如何做到的?同样,我正在寻找比一英里长的 switch 语句更优雅的东西)

它会生成真正的本机代码,将其加载到内存中并使其运行吗? (此时我猜它不再是解释器了,更像是 JIT 编译器)

另外,“虚拟机”的概念是在什么时候切入的?您在语言中使用虚拟机做什么? (要清楚我的无知程度,对我来说虚拟机是VMWare,我不知道VM的概念如何应用于编程语言/执行程序)。

如您所见,我的问题非常广泛。我主要不仅在寻找使用哪种方法,而且主要是首先了解大概念,然后详细了解它的工作原理。我想要丑陋的原始细节。显然,这更多的是寻求参考要阅读的内容,而不是期望您在这里回答所有这些细节。

谢谢! 丹尼尔


编辑:到目前为止,感谢您的回答。但我意识到我的标题具有误导性。我了解编译器和解释器之间的“功能”差异。 我正在寻找的是您实现解释器与编译器的区别。 我现在了解编译器是如何实现的,问题是解释器与它有何不同。

例如:VB6 显然既是编译器又是解释器。我现在了解编译器部分。但是,我无法理解,当在 IDE 中运行时,它可以让我在任意点停止程序、更改代码并使用新代码恢复执行。 这只是一个很小的例子,这不是我正在寻找的答案。正如我在下面解释的那样,我试图理解的是在我拥有解析树之后会发生什么。编译器将从它以“目标”语言生成新代码。口译员是做什么的?

感谢您的帮助!

【问题讨论】:

我可以提出一个不同的问题 - 我如何实现解释器?编辑和继续可以是解释或编译功能。 【参考方案1】:

两者有很多共同点(例如词法解析器),并且在差异上存在分歧。我是这样看的:

经典定义是编译器解析符号流并将其转换为可由 CPU 运行的字节流,而解释器执行相同的操作,但将它们转换为必须在某个软件(例如 JVM、CLR)上执行。

然而人们称 'javac' 为编译器,因此编译器的非正式定义是必须将源代码作为一个单独的步骤来完成的,而解释器没有“构建”步骤(例如 php、 Perl)。

【讨论】:

我会 +1 你,但有很多复杂性。例如,python 解释器有一个构建步骤(在需要时将 py 编译为 pyc)。此外,一些 CPU 将微码作为它们的“真实”语言,选择在此之上模拟“可见”CPU)。 解释器不会将输入转换为“必须在软件上执行的形式”——它可能会或可能不会在内部转换输入,但重点是它结束起来执行代码!就在那儿! 大多数人会将 PHP 视为解释器,但我向您保证它将纯文本源代码编译为中间形式。毕竟,这就是像 APC 这样的 PHP 加速器(操作码缓存)的工作原理。【参考方案2】:

它不像以前那样清晰。它曾经是构建一个解析树,绑定它,然后执行它(通常在最后一秒绑定)。

BASIC 主要是这样完成的。

您可以声称运行字节码 (java/.net) 而不执行 JIT 的东西是解释器 - 但不是传统意义上的,因为您仍然必须“编译”为字节码。

老派的区别是:如果它生成 CPU 代码,它就是一个编译器。如果您直接在编辑环境中运行它并且可以在编辑时与之交互,那么它就是一个解释器。

这远没有真正的龙书那么正式 - 但我希望它提供信息。

【讨论】:

您能否详细说明“绑定”的含义?还有,绑定后如何执行?【参考方案3】:

编译器是将一种编程语言的程序翻译成另一种编程语言的程序的程序。就是这样 - 简单明了。

解释器将编程语言翻译成它的语义。

x86 芯片是 x86 机器语言的解释器。

Javac 是一个java 到java 虚拟机的编译器。可执行应用程序 java 是 jvm 的解释器。

一些解释器共享一些编译元素,因为他们可能将一种语言翻译成另一种更容易解释的内部语言。

解释器通常(但并非总是)具有读取-评估-打印循环。

【讨论】:

read-eval-print 循环:这听起来很有趣,我会做一些阅读。谢谢!【参考方案4】:

如果我的经验表明了什么;

    解释器不会尝试进一步减少/处理 AST,每次引用代码块时,都会遍历并执行相关的 AST 节点。编译器最多会遍历一个块多次以在确定的位置生成可执行代码并完成它。 解释器的符号表在执行时保存值和引用,编译器的符号表保存变量的位置。执行时没有符号表这样的东西。

在镜头中的区别可能很简单

case '+':
    symtbl[var3] = symtbl[var1] + symtbl[var2];
    break;

之间,

case '+':
    printf("%s = %s + %s;",symtbl[var3],symtbl[var1],symtbl[var2]);
    break;

(如果您以其他语言或(虚拟)机器指令为目标,则无关紧要。)

【讨论】:

所以基本上,它是树遍历,加上一个非常巨大的 switch 语句? 如果您的实现语言支持具有虚拟方法的类,则不一定是可见开关。但是,是的,不同之处在于您在遍历树时所做的事情。【参考方案5】:

简短回答:

编译器将源代码转换为可执行格式以供以后执行 解释器评估源代码以立即执行

两者的实施方式都有很大的余地。解释器可以生成本地机器代码然后执行它,而虚拟机的编译器可以生成 p 代码而不是机器代码。像 Forth 这样的线程解释语言在字典中查找关键字并立即执行其相关的本机代码函数。

编译器通常可以更好地优化,因为他们有更多时间研究代码并生成文件以供以后执行;解释器的优化时间较少,因为他们倾向于在第一眼看到“原样”执行代码

在后台优化的解释器,学习更好的代码执行方式也是可能的

总结:真正的区别在于“准备代码以供以后执行”或“立即执行代码”

【讨论】:

现在,如果解释器生成机器码并执行它,那与编译器有什么不同呢?只是每次都编译吗? 另外,您是否有更多关于如何实现解释器的信息?我能读到的东西?谢谢! @[Daniel Magliola]: re #1 是的,每次都编译,不输出编译结果reust;但实际上很少有口译员这样做,每次重做所有工作的成本非常高 @[Daniel Magliola]:关于 #2 解释器的核心是立即执行输入源代码的“评估”或“执行”功能;有很多方法可以做到这一点。最好的办法是查看解释器对您感兴趣的每种语言的工作方式 - basic、python、lisp 等。 龙之书在第一章的早期就回答了这个问题。【参考方案6】:

程序是对您想要完成的工作的描述。

编译器将高级描述转换为更简单的描述。

口译员阅读关于做什么的描述并完成工作

一些解释器(例如 Unix shell)一次读取一小段描述,并根据他们看到的每一段进行操作;有些人(例如 Perl、Python)阅读整个描述,在内部将其转换为更简单的形式,然后采取行动。 一些解释器(例如 Java 的 JVM 或 Pentium 4 芯片)只能理解一种非常简单的描述语言,这对于人类来说太繁琐而无法直接使用,因此人类使用编译器将其高级描述转换为这种语言。

编译器从不做这项工作。口译员总是做这项工作。

【讨论】:

您提到处理器作为解释器很有趣。这确实给了我一个新的观点。 谢谢,实际上 plinth 是第一个这样描述它的人。【参考方案7】:

鉴于您的步骤列表:

词法分析器 解析器(构建语法树) 生成中间码(如3地址码) 如果你愿意,可以做所有这些疯狂的事情来优化:-) 从 3 地址码生成“程序集”或“本机代码”。

一个非常简单的解释器(如早期的 BASIC 或 TCL)一次只能执行第 1 步和第 2 步。然后在继续执行下一行时丢弃大部分结果。其他 3 个步骤根本不会执行。

【讨论】:

【参考方案8】:

关于您问题的这一部分,其他答案尚未真正解决:

另外,这个概念在什么时候 “虚拟机”的切入点?做什么 您使用虚拟机 语言?

像 JVM 或 CLR 这样的虚拟机是一个抽象层,允许您对编译后在 VM 上运行的完全不同的语言重用 JIT 编译器优化、垃圾收集和其他实现细节。

它们还可以帮助您使语言规范更加独立于实际硬件。例如,虽然 C 代码在理论上是可移植的,但如果您真的想要生成可移植代码,则必须经常担心字节顺序、类型大小和变量对齐等问题。而在 Java 中,JVM 在这些方面有非常明确的规定,因此语言设计者和它的用户不必担心它们;在实际硬件上实现指定行为是 JVM 实现者的工作。

【讨论】:

谢谢,很清楚。现在,为了确保我理解,VM 只是为其生成代码的“目标机器”,它将获取您的“编译”代码,然后为真机编译或解释并运行它,对吗?这就像将编译分成两个独立的步骤?【参考方案9】:

一旦解析树可用,就有几种策略:

1) 直接解释AST(Ruby,WebKit的原始解释器) 2) 代码转换 -> 成字节码或机器码

要实现编辑并继续,程序计数器或指令指针必须重新计算和移动。这需要 IDE 的配合,因为代码可能已经插入到黄色小箭头之前或之后。

可以做到这一点的一种方法是将程序计数器的位置嵌入到解析树中。例如,可能有一个名为“break”的特殊语句。程序计数器只需定位在“break”指令之后即可继续运行。

此外,您必须决定要对当前堆栈帧(以及堆栈上的变量)执行什么操作。可能会弹出当前堆栈,并复制变量,或者保留堆栈,但修补 GOTO 并返回到当前代码。

【讨论】:

【参考方案10】:

如果您正在寻找一本书,Structure and Interpretation of Computer Programs(“向导书”)是从解释器概念开始的好地方。您只需要处理可以像 AST 一样遍历、评估和传递的 Scheme 代码。

另外,Peter Norvig 有一个简短的示例,解释了使用 Python 的主要思想(在 cmets 中有更多示例),这是***上的另一个 small example。

就像你说的,它是一个树遍历,至少对于按值调用来说它是一个简单的:每当你看到一个操作符,评估操作数的拳头,然后应用操作符。返回的最终值是程序的结果(或给 REPL 的语句)。

请注意,您不必总是明确地进行树遍历:您可以以接受访问者的方式生成 AST(我认为 SableCC 会这样做),或者对于非常小的语言,例如小型算术语法用于演示解析器生成器,您可以在解析期间评估结果。

为了支持声明和赋值,你需要保持一个环境。正如您通过添加操作数来评估“加号”一样,您可以通过在环境中查找来评估函数、变量等的名称。支持范围意味着将环境视为堆栈并在正确的时间推送和弹出内容。通常,您的解释器有多复杂取决于您要支持的语言功能。例如,解释器使垃圾收集和自省成为可能。

对于 VM:plinth 和 j_random_hacker 将计算机硬件描述为一种解释器。反之亦然——口译员是机器;他们的指令恰好比真正的 ISA 的指令级别更高。对于 VM 风格的解释器,这些程序实际上类似于机器代码,尽管是非常简单的机器。 Java bytecode 只使用几个“寄存器”,其中一个包含程序计数器。因此,与我上面链接的示例中的解释器相比,VM 解释器更像是硬件模拟器。

但请注意,出于速度原因,默认的 Oracle JVM 会将 Java 字节码指令的运行转换为 x86 指令(“即时编译”)。

【讨论】:

以上是关于实现编译器和解释器有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

linux中shell分多少种,都有啥区别

麻烦解释一下linux下进程和线程有啥区别和联系,linux下多线程和多进程通信的实现方法,请通俗解释

PHP 解释器和 PHP 处理程序有啥区别?

编译器中的“语法”和“语法”有啥区别?

Python IDLE与python有啥区别

RyuJIT 和 Roslyn 有啥区别?