编译型语言 vs 解释型语言

322

我试着更好地理解它们的区别。我在网上找到了很多解释,但它们倾向于抽象的差异而不是实际的影响。

我的大部分编程经验都是使用CPython(动态、解释型)和Java(静态、编译型)。然而,我知道还有其他种类的解释型和编译型语言。除了可以从编译型语言编写的程序分发可执行文件的事实外,每种类型都有什么优缺点吗?通常情况下,我听到人们争论解释型语言可以以交互方式使用,但我相信编译型语言也可以有交互式实现,对吧?


37
你选择的是这个比较最差的语言。两者都是字节编译的。它们之间唯一真正的区别在于JIT编译器,甚至Python也有部分JIT编译器(psyco)。 - Ignacio Vazquez-Abrams
1
一个交互式编译语言的好例子是Clojure,它将所有内容进行完全编译(首先到JVM然后通过JIT转换为本机代码)。但是,许多重新编译是动态发生的,并且开发通常在交互式REPL shell中完成,在其中您可以在运行环境中评估任何想要的函数。 - mikera
Standard ML是另一种交互式编译语言;内置编译器也会生成真正的本机机器代码。 - Donal Fellows
2
http://publib.boulder.ibm.com/infocenter/zos/basics/index.jsp?topic=/com.ibm.zos.zappldev/zappldev_85.htm - pvllnspk
仅仅因为 OP 提到了两种语言作为例子,并不意味着问题是关于这两种语言的。已经移除了这些标签。我可以添加“语言无关”的标签,但这已经隐含在“编译器构造”和“编程语言”中了。 - Karl Knechtel
13个回答

512
编译语言是一种一旦编译完成,程序就以目标计算机的指令表示的语言。例如,源代码中的加法"+"操作可以直接翻译为机器码中的"ADD"指令。
解释语言是指指令不直接由目标机器执行,而是由其他程序读取和执行的语言(这通常是用本机语言编写的)。例如,同样的"+"操作将在运行时被解释器识别,然后使用适当的参数调用自己的"add(a,b)"函数,然后执行机器码的"ADD"指令。
在编译语言和解释语言中都可以做任何可以做的事情-它们都是图灵完备的。但两者在实现和使用方面都有优缺点。
我要彻底概括(专业人士请原谅!),但大致上,编译语言的优点如下:
·通过直接使用目标机器的本机代码,性能更快
·编译阶段有机会应用相当强大的优化
解释语言的优点如下:
·更容易实现(编写好的编译器非常难!)
·无需运行编译阶段:可以直接“即时执行”代码
·对于动态语言可能更方便
需要注意的是,现代技术(如字节码编译)增加了一些额外的复杂性-这里发生的情况是编译器针对不同于底层硬件的“虚拟机”进行目标。然后,可以在以后的阶段再次编译这些虚拟机指令以获得本地代码(例如由Java JVM JIT编译器执行的操作)。

2
@Kareem:JIT编译器只会执行1)和2)一次 - 之后就完全是本地代码了。解释器需要每次调用代码时都执行1)和2)(可能会有很多次...)。因此,随着时间的推移,JIT编译器的优势将远远超过解释器。 - mikera
3
是的,字节码在整个程序执行过程中的某个时刻被翻译为机器码(与传统编译器在程序执行之前的情况不同)。但是在整个程序执行过程中,某个给定的代码片段可能会执行1000多万次。它(可能)只会从字节码编译一次到机器码。因此,JIT的运行时开销很小,可以忽略长时间运行的程序。在JIT编译器完成其工作后,你将有效地一直运行纯机器码。 - mikera
3
其实,这是一个错误的二分法。语言本身没有使其被编译或解释的固有特性,这只是一种广泛存在的误解。许多语言都有两种实现方式,而所有语言都可以选择其中任意一种。 - mmachenry
2
@mmachenry 这不是虚假二分法。"编程语言"包括设计和实现两个方面。尽管在理论上,某种语言定义可以同时被编译和解释,但在实际应用中,实现方式存在相当大的差异。例如,目前还没有人能够有效地编译某些语言结构,这是一个开放的研究问题。 - mikera
2
@mikera 确实如此。编译和解释都有其优点。尽管编译器技术正在发展以改进某些语言特性,但不能因此就说编译具有该特性的语言的优势。将语言与实现混淆会导致我们错误地选择编译或解释实现。例如,你的评论“[解释器]对于动态语言可能更方便”。 - mmachenry
显示剩余9条评论

109

一门语言本身既不是编译型的也不是解释型的,只有语言的具体实现才是。Java就是一个完美的例子。它有基于字节码的平台(JVM)、本地编译器(gcj)以及一个针对Java超集的解释器(bsh)。那么Java现在是什么?是字节码编译、本地编译还是解释执行?

其他既编译又解释的语言包括Scala、Haskell或Ocaml。这些语言都有交互式解释器,同时也有编译器将代码编译成字节码或本地机器码。

因此,通常按“编译型”和“解释型”来分类语言并没有太多意义。


3
我同意。或者说:有本地编译器(生成CPU可执行的机器码),还有不那么本地化的编译器(生成记号化的中间代码,即某些即时编译器在运行时(或运行期间)编译成机器码),还有“真正”的非编译器,它们从不产生机器码,也不让CPU运行代码。后者是解释器。如今,直接在编译时生成机器(CPU)代码的本地编译器越来越少见了。Delphi/Codegear是最好的幸存者之一。 - TheBlastOne

65

开始从一个过去的角度思考:过去的影响

很久很久以前,在计算机译者和编译器的土地上生活着。各种各样的争论围绕着一个比另一个更好的优点展开。当时的普遍观点大致是:

  • 译者:快速开发(编辑和运行)。执行速度慢,因为每次执行语句都必须将其解释成机器代码(想象一下对于执行数千次的循环意味着什么)。
  • 编译器:开发速度慢(编辑、编译、链接和运行。编译/链接步骤可能需要很长时间)。执行速度快。整个程序已经是本机机器代码。

在解释程序和编译程序之间存在一到两个数量级的运行时性能差异。其他区别点,例如代码的运行时可变性,也具有一定的兴趣,但主要区别围绕运行时性能问题。

今天,局面已经发展到了编译和解释的区别基本上无关紧要。许多编译语言调用的运行时服务不完全基于机器代码。此外,大多数解释语言在执行之前都会“编译”为字节码。字节码解释器可以非常高效,并在执行速度上与一些编译器生成的代码相媲美。

经典的区别是编译器生成本机机器代码,解释器读取源代码并使用某种运行时系统即时生成机器代码。今天几乎没有经典的解释器了 - 几乎所有解释器都编译成字节码(或其他半编译状态),然后在虚拟“机器”上运行。


28

极端简单的情况:

  • 编译器将生成使用目标机器本地可执行格式的二进制可执行文件。除了系统库之外,这个二进制文件包含所有所需资源;它已经准备好运行,没有进一步的准备和处理,并且因为代码是目标机器 CPU 的本机代码,所以它运行非常快。

  • 解释器会在循环中呈现一个提示符,用户可以在其中输入语句或代码,然后按下RUN或等效项,解释器将检查、扫描、解析并逐行执行每个程序,直到程序运行到停止点或错误。由于每行都是独立处理的,而解释器不会从之前看到的代码中“学习”任何东西,所以将人类可读语言转换为机器指令的工作需要针对每行每次重复进行,因此速度比较慢。但是,用户可以以各种方式检查和互动他的程序:更改变量、更改代码、以跟踪或调试模式运行等。

这些已经弄清楚了,现在让我解释一下,生活不再那么简单了。例如,

  • 许多解释器将预编译它们收到的代码,以便不必反复进行翻译步骤。
  • 一些编译器不会编译为特定于 CPU 的机器指令,而是编译为字节码,即虚构机器的一种人工机器代码。这使得编译后的程序更具可移植性,但需要在每个目标系统上运行一个字节码解释器。
  • 字节码解释器(我在看 Java)最近倾向于在执行之前为目标区域的 CPU 重新编译它们所接收到的字节码(称为 JIT)。为了节省时间,这通常只针对经常运行的代码(热点)进行。
  • 某些看起来和表现像解释器的系统(例如 Clojure)立即编译其收到的任何代码,但允许交互式访问程序的环境。这基本上是结合了编译器的速度和解释器的便利。
  • 有些编译器实际上并不编译,只是预处理和压缩代码。我听说过 Perl 就是这样工作的。因此,有时编译器只是做了一点工作,而大部分工作仍然是解释。

现在,解释和编译已经成为一个权衡取舍的问题。编译代码只需花费一次时间,但可以获得更好的运行时性能;而解释环境更加交互式。编译与解释的主要区别是将"理解"程序的工作如何分配给不同的过程,如今随着语言和产品试图提供最佳方案,两者之间的界限已经变得模糊。


27
http://www.quora.com/What-is-the-difference-between-compiled-and-interpreted-programming-languages阅读。
没有区别,因为“编译型编程语言”和“解释型编程语言”不是有意义的概念。任何一种编程语言,我真的是任何一种,都可以被解释或编译。因此,解释和编译是实现技术,而不是语言属性。
解释是一种技术,通过该技术,另一个程序,即解释器,代表正在被解释的程序执行操作以运行它。如果你能想象阅读一个程序并逐步按照其指示进行操作,例如在草稿纸上,那么解释器也会这样做。解释程序的常见原因是解释器相对容易编写。另一个原因是解释器可以在运行时监视程序尝试执行的操作,以执行策略,例如安全策略。
编译是一种技术,其中用一种语言(源语言)编写的程序被翻译为另一种语言(目标语言)编写的程序,希望它与原始程序具有相同的含义。在进行翻译时,编译器通常还会尝试以使目标程序更快(而不改变其含义!)的方式转换程序。编译程序的常见原因是在不沿途解释源语言的开销的情况下以某种良好的方式快速运行目标语言中的程序。
您可能已经根据上述定义猜到,这两种实现技术并不是互斥的,甚至可能是互补的。传统上,编译器的目标语言是机器代码或类似的东西,它指的是特定计算机CPU理解的任何数量的编程语言。然后,机器代码将在“金属”上运行(尽管如果仔细观察,人们可能会发现,“金属”工作方式很像解释器)。但是,今天非常普遍的做法是使用编译器生成旨在被解释的目标代码-例如,这就是Java以前(有时仍然)工作的方式。还有将其他语言转换为JavaScript的编译器,然后通常在Web浏览器中运行JavaScript,该浏览器可能会解释JavaScript,也可能会将其编译为虚拟机或本地代码。我们还有用于机器代码的解释器,可用于在另一种硬件上模拟一种硬件。或者,可以使用编译器生成目标代码,然后将其作为另一个编译器的源代码,该编译器甚至可以在内存中及时编译代码以便其运行,而这反过来又...你明白了吧。有许多方法可以结合这些概念。

你能修正这个句子吗:“有一些编译器可以将其他语言翻译成JavaScript,然后通常在Web浏览器中运行,可能会解释JavaScript,或将其编译为虚拟机或本地代码。” - Koray Tugay
做得好。另一个常见的错误是将语言的有用性归因于其现有的API。 - Little Endian

11
解释性源代码相对于编译后的源代码最大的优势是可移植性
如果您的源代码已经编译,那么您需要为每种类型的处理器和/或平台编译不同的可执行文件,以便您的程序运行(例如,为Windows x86编译一个,为Windows x64编译一个,为Linux x64编译一个等)。此外,除非您的代码完全符合标准并且不使用任何平台特定的函数/库,否则您实际上需要编写和维护多个代码库!
如果您的源代码是解释性的,您只需要编写一次,在任何平台上都可以通过适当的解释器进行解释和执行!它是可移植的!请注意,解释器本身是针对特定平台编写和编译的可执行程序。
编译代码的优点是它可以从最终用户中隐藏源代码(可能是知识产权),因为您部署的是一个难以理解的二进制可执行文件,而不是原始的人类可读源代码。

1
在这种条件下,Java 不能被认为是一种“编译语言”,但它的编译阶段具有编译的优点(类型检查、早期错误检测等),并且生成的字节码可以在每个操作系统上运行,只要提供了 Java 虚拟机。 - Rogelio Triviño

8
编译器和解释器的工作相同:将编程语言翻译成另一种编程语言,通常更接近硬件,往往是直接可执行的机器代码。
传统上,“编译”意味着这个翻译过程一次性完成,由开发人员完成,并将生成的可执行文件分发给用户。一个纯粹的例子是C ++。编译通常需要相当长的时间并尝试进行大量昂贵的优化,以使生成的可执行文件运行得更快。最终用户没有编译自己的工具和知识,并且可执行文件通常必须在各种硬件上运行,因此您不能进行许多硬件特定的优化。在开发过程中,单独的编译步骤意味着反馈周期更长。
传统上,“解释”意味着翻译在用户想运行程序时“即时”发生。纯粹的例子是vanilla PHP。一个天真的解释器必须每次运行时解析和翻译每一段代码,这使其非常缓慢。它不能进行复杂的、昂贵的优化,因为它们需要比执行节省的时间更长。但它可以充分利用所运行的硬件的能力。缺少单独的编译步骤减少了开发过程中的反馈时间。
但现在,“编译 vs 解释”不是一个黑白问题,其中有一些纷繁复杂的变化。天真的、简单的解释器已经基本绝迹。许多语言使用两步法,高级代码被翻译成平台无关的字节码(这比较快),然后你有“即时编译器”,它最多每个程序运行一次编译代码,有时缓存结果,甚至聪明地决定解释很少运行的代码,并为经常运行的代码进行强大的优化。在开发过程中,调试器可以在传统编译语言的运行程序中切换代码。

1
然而,C++的编译模型继承自C,并且在设计时没有考虑到模板等特性。这种笨拙导致了C++的编译时间比任何其他因素都要长,使其成为一个糟糕的例子。 - Roger Pate

7

我认为这是计算机科学中最被误解的事情之一。因为解释和编译完全是两个不同的过程,我们不能用这种方式进行比较。

编译是将一种语言翻译成另一种语言的过程。有几种类型的编译。

  • 编译 - 将高级语言翻译为机器/字节码(例如:C/C++/Java)
  • 转译 - 将高级语言翻译为另一种高级语言(例如:TypeScript)

解释是实际执行程序的过程。这可能以几种不同的方式发生。

  • 机器级别解释 - 这种解释发生在已经编译成机器代码的代码上。指令由处理器直接解释。像 C/C++ 这样的编程语言生成的是可由处理器执行的机器代码。因此,处理器可以直接执行这些指令。

  • 虚拟机级别解释 - 这种解释发生在未编译为机器级别(处理器支持)代码而是编译为某些中间级别代码的代码上。这个执行是由另一个软件执行的,它是由处理器执行的。此时,处理器实际上看不到我们的应用程序。它只是执行虚拟机,而虚拟机在执行我们的应用程序。像 Java、Python、C# 这样的编程语言生成的是可由虚拟解释器/机器执行的字节码。

因此,最终我们必须理解的是,世界上所有编程语言都应该在某个时刻被解释。 它可以由处理器(硬件)或虚拟机执行。

编译只是将我们编写的高级代码(可被人类理解的)转换为某种硬件/软件可理解的水平的过程。

这些完全是两回事,我们无法进行比较。但是这种术语对于教授初学者如何处理编程语言非常有用。

PS:
一些编程语言如 Java 采用混合方法来实现这一点。首先,将高级代码编译成虚拟机可读的字节码。然后,在运行过程中,一个名为 JIT 编译器的组件将字节码编译成机器代码。特别是经常执行的代码行被翻译为机器语言,这使得解释过程更快。因为硬件处理器总是比虚拟解释器/处理器快得多。

Java JIT 编译器的工作原理


5
首先,需要澄清的是,Java不是像C++那样完全进行静态编译和链接。它被编译成字节码,然后由JVM进行解释。JVM可以进行及时编译成本机语言,但不一定必须这样做。
更重要的是:我认为互动性是主要的实际区别。由于所有内容都是解释执行的,您可以取出一小段代码,解析并根据当前环境状态运行它。因此,如果您已经执行了初始化变量的代码,则可以访问该变量等。它确实非常适合函数式风格之类的东西。
然而,解释执行的成本很高,特别是当您有一个具有许多引用和上下文的大型系统时。根据定义,这是浪费的,因为相同的代码可能必须被解释和优化两次(尽管大多数运行时对此具有某些缓存和优化)。仍然需要支付运行时成本,并且通常需要运行时环境。您也不太可能看到复杂的过程间优化,因为目前它们的性能还不足以交互。
因此,对于大型系统而言,如果不会发生太多更改,并且对于某些语言而言,将所有内容预编译和预链接起来,并执行所有可以执行的优化,这样就可以得到非常精简的运行时环境,已经为目标机器进行了优化。
至于生成可执行文件,那与此无关。在大多数情况下,您可以从编译语言创建可执行文件。但是,您也可以从解释性语言创建可执行文件,只是解释器和运行时环境已经打包在可执行文件中并对您隐藏。这意味着通常仍然需要支付运行时成本(尽管我相信对于某些语言,有办法将所有内容转换为树形式的可执行文件)。
我不同意所有语言都可以交互的说法。某些语言,例如C,与机器和整个链接结构紧密相关,因此我不确定是否可以构建一个有意义的完全交互版本。

C语言并不是真正与“机器”绑定的。C语言的语法和语义相当简单。实现一个C解释器应该不会特别困难,只是非常耗时(因为标准库也必须被实现)。另外,Java可以编译成本地机器代码(使用gcj)。 - user355252
@lunaryorn:我不同意关于GCJ的看法。GCJ只是提供了一个基于可执行文件的环境。"编译后的应用程序与GCJ运行时库(libgcj)链接,该库提供核心类库、垃圾回收器和字节码解释器。" - Uri
2
GCJ确实生成本机机器代码,而不仅仅是带有嵌入式解释器和字节码的可执行环境。libgcj提供了一个字节码解释器,以支持从本机代码调用Java字节码,而不是解释编译后的程序。如果libgcj没有提供字节码解释器,GCJ将不符合Java规范。 - user355252
@lunaryorn:啊。好的,我感谢你的澄清并且改正了我的错误。由于我们主要在Windows环境下使用Java,所以我已经多年没有尝试使用gcj了。 - Uri
2
你可以解释C语言。这是三个链接:Youcaninterpret - Roger Pate

3

《Python Book》© 2015 Imagine Publishing Ltd,通过第10页提到的以下提示来简单区分:

解释型语言(如Python)是指每次程序运行时源代码都会被转换为机器码然后执行。这与编译型语言(如C)不同,编译型语言只在源代码被转换为机器码一次后,每次程序运行时直接执行生成的机器码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接