编译与解释有什么区别?
编译与解释有什么区别?
我刚刚和一个同事谈到了 V8 JavaScript 引擎。根据维基百科,
V8 将 JavaScript 编译成本机机器代码[…]在执行之前,而不是更传统的技术,如解释字节码或将整个程序编译为机器代码并从文件系统执行。
在这里(如果我错了请纠正我),“解释字节码”是 Java 的工作方式,“编译整个程序”则适用于像 C 或 C++ 这样的语言。现在我们正在探讨并提出了假设和推论以了解它们之间的差异和相似之处。为了结束这一切,我建议向 SO 上的专家提问。
那么,谁能够
- 命名、解释和/或引用所有主要方法(例如预编译与运行时解释)
- 可视化或提供有关源、编译和解释之间关系的方案
- 为问题1中的主要方法提供示例(命名编程语言)。
注意:
- 我不是在寻找有关不同范式的长篇散文,而是需要一个受视觉支持的快速概述。
- 我知道 Stackoverflow 并不是为程序员编写百科全书而设计的(而是专为更具体的问题的问答平台)。但由于我可以找到很多受欢迎的问题,这些问题在某种意义下提供了对特定主题的百科全书式观点(例如[1],[2],[3],[4],[5]),我开始提出这个问题。
- 如果这个问题更适合其他 StackExchange 网站(例如cstheory)中的任何一个,请让我知道或标记此问题以供审核。
现在许多执行环境都使用字节码(或类似物)作为代码的中间表示。因此,源代码首先编译成一种中间语言,然后由虚拟机解释(解码字节码指令集),或进一步编译成机器代码并由硬件执行。
目前很少有生产语言不会在预编译成某些中间形式之前进行解释。但是,很容易构想这样一个解释器:只需考虑一个类层次结构,它为每种语言元素(if
语句、for
等)的子类,每个类都有一个Evaluate
方法,该方法计算给定的节点。这也通常称为解释器设计模式。
以实现C#的if
语句为例,考虑以下代码片段:
class IfStatement : AstNode { private readonly AstNode condition, truePart, falsePart; public IfStatement(AstNode condition, AstNode truePart, AstNode falsePart) { this.condition = condition; this.truePart = truePart; this.falsePart = falsePart; } public override Value Evaluate(EvaluationContext context) { bool yes = condition.Evaluate(context).IsTrue(); if (yes) truePart.Evaluate(context); else falsePart.Evaluate(context); return Value.None; // `if` statements have no value. } }
这是一个非常简单但完全功能的解释器。
回答你的问题几乎是不可能的,因为这不是几种方法,而是一种连续的过程。这个连续过程中涉及的实际代码非常相似,唯一的区别在于何时发生事情,以及是否以某种方式保存中间步骤。这个连续过程的不同阶段(它不是一条单线、一个进展,而是一个具有不同角落的矩形)包括:
- 读源代码
- 理解代码
- 执行理解的内容
- 缓存沿途各种中间数据,或甚至将它们持续保存到磁盘中。
例如,一个纯粹的解释型编程语言基本上不涉及第4步,第2步在1和3之间含蓄地发生,你几乎不会注意到它。它只是读取代码的各个部分,立即作出反应。这意味着实际上启动执行的开销很小,但在循环中,同一行文本会被反复读取。
在矩形的另一个角落,通常有传统的编译型语言,其中第4项通常包括将实际机器代码永久保存到文件中,然后可以在以后的某个时间运行它。这意味着在开始执行之前需要等待相对较长的时间(即使只调用其中的一个函数),但在循环中速度更快,因为不需要重新读取源代码。
还有一些中间环节,例如一个虚拟机:为了实现可移植性,许多编程语言并不会直接编译成机器码,而是编译成字节码。然后有一个编译器生成字节码,并且一个解释器接收这个字节码并实际运行它(实际上是“将其转变为机器码”)。尽管这通常比直接编译成机器码慢,但将这样的语言移植到另一个平台更容易,因为您只需要移植字节码解释器,而字节码解释器通常是用高级语言编写的,这意味着您可以使用现有的编译器来做这个“有效的机器码转换”,而不必为您要运行的每个平台制作和维护后端。此外,在某些情况下,如果您可以将编译到字节码的代码编译一次,然后只分发已编译的字节码,那么其他人就不必在运行代码优化器时花费CPU周期,并且只需要支付将字节码转换为本机代码的成本,这在您的使用情况下可能可以忽略不计。还有,您不会泄露源代码。
另一个中间环节是即时编译器(JIT),它有效地是一个运行过一次代码的解释器,以已编译的形式进行保留。这种“保留”使其比一个纯解释器慢一些(例如增加了开销和RAM使用导致换入换出和磁盘访问),但是当重复执行一段代码时,它会更快。对于只重复调用单个函数的代码,它也可能比一个纯编译器更快,因为如果不使用其余的程序,它不会浪费时间编译它们。
最后,你可以通过不永久保存已编译的代码,而是再次清除缓存中的已编译代码在此矩形上发现其他的点。这样,你可以节省嵌入式系统中的磁盘空间或RAM,但可能需要再次编译很少使用的代码。许多JIT编译器都采用这种方法。