23

我最近阅读了整本《龙之书》(只是为了好玩,我并没有真正打算实现一个实际的编译器),我的脑海中留下了一个悬而未决的大问题。

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

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

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

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

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

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

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

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

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

谢谢!
丹尼尔


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

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

感谢您的帮助!

4

10 回答 10

8

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

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

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

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

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

解释器通常(但并非总是)具有 read-eval-print 循环。

于 2009-01-24T01:01:39.663 回答
8

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

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

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

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

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

于 2009-01-24T04:04:18.327 回答
7

简短的回答:

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

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

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

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

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

于 2009-01-24T01:24:27.357 回答
4

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

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

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

于 2009-01-24T00:53:18.103 回答
3

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

BASIC 主要是这样完成的。

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

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

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

于 2009-01-24T00:54:09.040 回答
2

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

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

在镜头中,差异可能很简单

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

之间,

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

(如果您以另一种语言或(虚拟)机器指令为目标,这并不重要。)

于 2009-01-24T01:24:00.867 回答
2

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

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

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

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

于 2009-01-26T15:14:33.567 回答
1

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

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

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

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

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

于 2009-02-04T00:29:14.373 回答
0

鉴于您的步骤列表:

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

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

于 2009-01-26T15:12:25.753 回答
0

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

此外,Peter Norvig有一个简短的示例来解释使用 Python 的主要思想(评论中有更多示例),这是 Wikipedia 上的另一个小示例

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

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

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

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

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

于 2014-05-11T06:48:20.887 回答