【问题标题】:Compiled vs. Interpreted Languages编译语言与解释语言
【发布时间】:2011-03-16 23:22:09
【问题描述】:

我正在努力更好地了解差异。我在网上找到了很多解释,但它们倾向于抽象的差异而不是实际的含义。

我的大部分编程经验都是使用 CPython(动态、解释)和 Java(静态、编译)。但是,我知道还有其他类型的解释和编译语言。除了可执行文件可以从用编译语言编写的程序中分发之外,每种类型是否有任何优点/缺点?我经常听到人们争辩说解释语言可以交互使用,但我相信编译语言也可以有交互实现,对吗?

【问题讨论】:

  • 您选择了最差的语言进行比较。两者都是字节编译的。它们之间唯一真正的区别是 JITer,甚至 Python 也有一个部分 (psyco)。
  • Clojure 是交互式编译语言的一个很好的例子——一切都完全编译(首先编译到 JVM,然后通过 JIT 编译到本机代码)。然而,很多重新编译都是动态发生的,并且开发通常在交互式 REPL shell 中完成,您可以在其中评估运行环境中所需的任何功能。
  • 标准 ML 是另一种交互式编译语言;内置编译器也会发出真正的本机机器代码。

标签: java python compiler-construction programming-languages interpreter


【解决方案1】:

编译语言是一种程序,一旦编译,就会在目标机器的指令中表达。例如,源代码中的加法“+”操作可以直接转换为机器代码中的“ADD”指令。

解释性语言是一种指令不直接由目标机器执行,而是由其他程序读取和执行的语言(通常用本机机器的语言编写)。例如,解释器会在运行时识别相同的“+”操作,然后使用适当的参数调用自己的“add(a,b)”函数,然后执行机器代码“ADD”指令.

你可以用编译语言做任何你可以用解释语言做的事情,反之亦然——它们都是图灵完备的。然而,两者在实施和使用方面各有利弊。

我将完全概括(纯粹主义者原谅我!)但是,粗略地说,这是编译语言的优点:

  • 直接使用目标机器的本机代码,性能更快
  • 有机会在编译阶段应用非常强大的优化

以下是解释型语言的优势:

  • 更容易实现(编写好的编译器非常困难!!)
  • 无需运行编译阶段:可以直接“即时”执行代码
  • 对于动态语言可以更方便

请注意,字节码编译等现代技术会增加一些额外的复杂性——这里发生的情况是编译器针对的是与底层硬件不同的“虚拟机”。然后可以在稍后阶段再次编译这些虚拟机指令以获得本机代码(例如由 Java JVM JIT 编译器完成)。

【讨论】:

  • @Kareem:JIT 编译器只执行 1) 和 2) 一次 - 之后它一直是本机代码。每次调用代码时,解释器都需要执行 1) 和 2)(可能很多次……)。因此,随着时间的推移,JIT 编译器会大获全胜。
  • 是的,字节码在整个程序执行期间在某个时刻被转换为机器码(与传统编译器的情况相反,在程序执行之前)。但是在整个程序执行期间,一段给定的代码可能会执行 1000 万次以上。它(可能)只从字节码编译为机器码一次。因此 JIT 的运行时开销很小,对于长时间运行的程序可以忽略不计。在 JIT 编译器完成其工作后,您将一直有效地运行纯机器代码。
  • 这实际上是一种错误的二分法。语言本身并没有任何东西可以编译我们的解释。这只不过是一种普遍存在的误解。许多语言都有这两种实现,所有语言都可以有。
  • @mmachenry 这不是错误的二分法。 “编程语言”包括设计和实现。虽然在理论上给定的语言定义可以被编译和解释,但在现实世界的实践中在实现上有相当大的差异。还没有人解决如何有效地编译某些语言结构,例如 - 这是一个开放的研究问题。
  • @mikera 确实如此。编译有好处,解释也有好处。仅仅因为编译器技术正在开发以改进某些语言的特性并不意味着我们可以说任何关于编译具有该特性的语言的好处。将语言和实现混为一谈会导致我们对为实现选择编译或解释产生错误的理解。例如您的评论“[interpreters] 可以更方便地使用动态语言”
【解决方案2】:

一种语言本身既不编译也不解释,只有一种语言的特定实现才是。 Java 就是一个完美的例子。有一个基于字节码的平台(JVM)、一个本地编译器(gcj)和一个 Java 超集的解释器(bsh)。那么Java现在是什么?字节码编译、本机编译还是解释?

编译和解释的其他语言包括 Scala、Haskell 或 Ocaml。这些语言中的每一种都有一个交互式解释器,以及一个字节码或本机机器码的编译器。

所以通常按“编译”和“解释”对语言进行分类没有多大意义。

【讨论】:

  • 我同意。或者比方说:有本地编译器(创建供 CPU 使用的机器代码)和非本地编译器(创建标记化的东西,即中间代码,一些即时编译器在之前编译为机器代码(或在运行时 ONCE 期间),并且有“真正的”非编译器永远不会产生机器代码并且永远不会让 CPU 运行代码。后者是口译员。今天,在编译时直接生成机器(CPU)代码的本地编译器变得越来越少。 Delphi/Codegear 是最好的幸存者之一。
【解决方案3】:

从以下方面开始思考:过去的冲击

很久很久以前,在计算的土地上生活着 解释器和编译器。各种议论纷纷议论 一个比一个。 当时的普遍看法是这样的:

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

运行时的一个或两个数量级的差异 解释程序和编译程序之间存在性能。其他区别 点,例如代码的运行时可变性,也引起了人们的兴趣,但主要 区别在于运行时性能问题。

今天的情况已经发展到这样一个程度,编译/解释的区别是 几乎无关紧要。许多 编译语言调用的运行时服务不是 完全基于机器码。此外,大多数解释语言都被“编译”成字节码 在执行之前。字节码解释器可能非常高效,可以与某些编译器生成的相媲美 从执行速度的角度来看代码。

经典的区别是编译器生成本机机器代码,解释器读取源代码和 使用某种运行时系统即时生成机器代码。 今天剩下的经典口译员很少 - 几乎全部 编译成字节码(或其他一些半编译状态),然后在虚拟“机器”上运行。

【讨论】:

    【解决方案4】:

    极端简单的案例:

    • 编译器将以目标机器的本机可执行格式生成二进制可执行文件。此二进制文件包含除系统库以外的所有必需资源;它无需进一步准备和处理即可运行,并且运行起来就像闪电一样,因为代码是目标机器上 CPU 的本机代码。

    • 1234563程序运行到停止点或出错。因为每一行都是单独处理的,并且解释器不会从之前看到的行中“学习”任何东西,所以每次都需要为每一行将人类可读的语言转换为机器指令的工作,所以它很慢。从好的方面来说,用户可以通过各种方式检查他的程序并与之交互:更改变量、更改代码、在跟踪或调试模式下运行……等等。

    说完这些,让我解释一下,生活不再那么简单了。例如,

    • 许多解释器会预编译他们给出的代码,这样翻译步骤就不必一次又一次地重复。
    • 有些编译器不是编译为特定于 CPU 的机器指令,而是编译为字节码,一种用于虚拟机器的人工机器代码。这使得编译后的程序更易于移植,但在每个目标系统上都需要一个字节码解释器。
    • 字节码解释器(我在这里查看 Java)最近倾向于在执行之前重新编译它们为目标部分的 CPU 获取的字节码(称为 JIT)。为了节省时间,这通常只针对经常运行的代码(热点)。
    • 一些看起来和行为都像解释器的系统(例如 Clojure)会立即编译它们获得的任何代码,但允许对程序环境进行交互式访问。这基本上是解释器的便利与二进制编译的速度。
    • 有些编译器并不真正编译,它们只是预先消化和压缩代码。我听说不久前 Perl 就是这样工作的。所以有时编译器只是做了一点工作,大部分工作仍然是解释。

    最后,这些天来,解释与编译是一种权衡,花在(一次)编译上的时间通常会得到更好的运行时性能的回报,但解释性环境提供了更多的交互机会。编译与解释主要是关于“理解”程序的工作如何在不同进程之间划分的问题,而如今,随着语言和产品试图提供两全其美的方式,这条线有点模糊。

    【讨论】:

      【解决方案5】:

      来自http://www.quora.com/What-is-the-difference-between-compiled-and-interpreted-programming-languages

      没有区别,因为“编译型编程语言”和 “解释型编程语言”不是有意义的概念。任何 编程语言,我的意思是任何一种,都可以被解释或 编译。因此,解释和编译是实现 技术,而不是语言的属性。

      解释是一种技术,通过它另一个程序, 解释器,代表正在执行的程序执行操作 解释以运行它。如果你能想象阅读一个程序 并按照它所说的一步一步做,说在一块从头开始 纸,这正是口译员所做的。一个常见的原因 解释程序是解释器相对容易 写。另一个原因是解释器可以监控 程序在运行时尝试执行某项策略,例如, 安全。

      编译是一种使用一种语言编写程序的技术 (“源语言”)被翻译成另一种程序 语言(“对象语言”),希望是同一个意思 作为原始程序。在进行翻译时,常见的是 编译器也尝试以如下方式转换程序 使目标程序更快(不改变其含义!)。一种 编译程序的常见原因是有一些好方法 以目标语言快速运行程序,无需额外开销 一路解释​​源语言。

      根据上述定义,您可能已经猜到,这两个 实现技术并不相互排斥,甚至可能是 补充。传统上,编译器的目标语言是 机器码或类似的东西,指的是任意数量的 特定计算机 CPU 可以理解的编程语言。这 然后机器代码将“在金属上”运行(尽管人们可能会看到,如果 仔细一看,“金属”的工作原理很像 口译员)。然而,今天,使用编译器来 生成要被解释的目标代码——例如,这个 是 Java 过去(有时仍然如此)的工作方式。有 将其他语言翻译成 JavaScript 的编译器,然后 通常在 Web 浏览器中运行,该浏览器可能会解释 JavaScript,或者 将其编译为虚拟机或本机代码。我们也有口译员 用于机器码,可用于模拟一种硬件 其他。或者,可以使用编译器生成目标代码 然后是另一个编译器的源代码,它甚至可以编译 内存中的代码正好赶上它运行,这反过来。 . .你得到 这个想法。有很多方法可以组合这些概念。

      【讨论】:

      • 你能修正一下这句话吗:“有些编译器会将其他语言翻译成 JavaScript,然后通常在 Web 浏览器中运行,它可能会解释 JavaScript,或者将其编译为虚拟机或本机代码。”
      • 成功了。另一个常见的错误是将语言的有用性归因于其现有的 API。
      【解决方案6】:

      解释源代码相对于编译源代码的最大优势是可移植性

      如果您的源代码已编译,您需要为您希望程序运行的每种类型的处理器和/或平台编译不同的可执行文件(例如,一个用于 Windows x86,一个用于 Windows x64,一个用于 Linux x64 , 等等)。此外,除非您的代码完全符合标准并且不使用任何特定于平台的函数/库,否则您实际上需要编写和维护多个代码库!

      如果您的源代码被解释,您只需要编写一次,它就可以在任何平台上由适当的解释器解释和执行!它是便携!请注意,解释器本身是一个可执行程序,为特定平台编写和编译。

      编译代码的一个优点是它对最终用户隐藏源代码(可能是知识产权),因为它不是部署原始的人类可读源代码代码,你部署一个不起眼的二进制可执行文件。

      【讨论】:

      • 在这个方面java不能被认为是一种“编译语言”,但是它的编译阶段提供了编译的优势(类型检查,早期错误检测等),并且生成的字节码可以被在每个操作系统上运行,并提供 Java 虚拟机。
      【解决方案7】:

      编译器和解释器做同样的工作:将一种编程语言翻译成另一种编程语言,通常更接近硬件,通常是直接可执行的机器代码。

      传统上,“已编译”意味着此翻译一次完成,由开发人员完成,生成的可执行文件分发给用户。纯示例:C++。 编译通常需要很长时间并尝试进行大量昂贵的优化,以便生成的可执行文件运行得更快。最终用户没有自己编译东西的工具和知识,并且可执行文件通常必须在各种硬件上运行,因此您无法进行许多特定于硬件的优化。在开发过程中,单独的编译步骤意味着更长的反馈周期。

      传统上,“解释”意味着当用户想要运行程序时,翻译是“即时”发生的。纯示例:普通 PHP。一个幼稚的解释器必须在每次运行时解析和翻译每段代码,这使得它非常慢。它不能进行复杂、昂贵的优化,因为它们所花费的时间比执行所节省的时间要长。但它可以充分利用它所运行的硬件的功能。缺少单独的编译步骤减少了开发过程中的反馈时间。

      但如今,“编译与解释”并不是一个非黑即白的问题,两者之间存在阴影。天真、简单的解释器几乎绝迹了。许多语言使用两步过程,将高级代码转换为与平台无关的字节码(解释起来要快得多)。然后你有“即时编译器”,每次程序运行最多编译一次代码,有时缓存结果,甚至智能地决定解释很少运行的代码,并对运行很多的代码进行强大的优化。在开发过程中,即使是传统编译语言,调试器也能够在运行的程序中切换代码。

      【讨论】:

      • 但是,C++的编译模型继承自C,在设计时没有考虑模板等特性。与其他任何因素相比,这种笨拙对 C++ 编译时间长的影响要大得多 - 并使其成为一个糟糕的例子。
      【解决方案8】:

      首先,澄清一下,Java 并没有像 C++ 那样完全静态编译和链接。它被编译成字节码,然后由 JVM 解释。 JVM 可以对本地机器语言进行即时编译,但不必这样做。

      更重要的是:我认为交互性是主要的实际区别。由于所有内容都已解释,因此您可以提取一小段代码,针对当前环境状态进行解析和运行。因此,如果您已经执行了初始化变量的代码,那么您将可以访问该变量等。它确实适用于函数式风格。

      但是,解释的成本很高,尤其是当您拥有一个包含大量参考资料和上下文的大型系统时。根据定义,这是浪费的,因为相同的代码可能必须被解释和优化两次(尽管大多数运行时都有一些缓存和优化)。尽管如此,您还是需要支付运行时成本,并且通常需要运行时环境。您也不太可能看到复杂的过程间优化,因为目前它们的性能没有足够的交互性。

      因此,对于不会发生太大变化的大型系统以及某些语言,预编译和预链接所有内容更有意义,做所有可以做的优化。这最终会得到一个非常精简的运行时,该运行时已经针对目标机器进行了优化。

      至于生成可执行文件,这与它无关,恕我直言。您通常可以从编译语言创建可执行文件。但是您也可以从解释语言创建可执行文件,除非解释器和运行时已经打包在可执行文件中并且对您隐藏。这意味着您通常仍需支付运行时成本(尽管我确信对于某些语言,有办法将所有内容转换为树可执行文件)。

      我不同意所有语言都可以交互。某些语言,如 C,与机器和整个链接结构紧密相关,我不确定您是否可以构建一个有意义的成熟交互式版本

      【讨论】:

      • C 并没有真正绑定到“机器”。 C 的语法和语义相当简单。实现 C 解释器应该不是特别困难,只是非常耗时(因为还必须实现标准库)。顺便说一句,Java 可以编译成本机机器代码(使用 gcj)。
      • @lunaryorn:我不同意 GCJ。 GCJ 只是给你一个基于可执行的环境。 "编译后的应用程序与 GCJ 运行时 libgcj 链接,它提供核心类库、垃圾收集器和字节码解释器"
      • GCJ 确实生成本地机器代码,而不仅仅是具有嵌入式解释器和字节码的可执行环境。 libgcj 提供了一个字节码解释器来支持从本地代码到 Java 字节码的调用,而不是解释编译后的程序。如果 libgcj 没有提供字节码解释器,GCJ 将不符合 Java 规范。
      • @lunaryorn:啊。好的,我感谢您的澄清并纠正。我们主要在 windows 环境中使用 Java,所以我已经很多年没有尝试过 gcj 了。
      【解决方案9】:

      很难给出一个实际的答案,因为区别在于语言定义本身。可以为每种编译语言构建一个解释器,但不可能为每种解释语言构建一个编译器。这非常关乎语言的正式定义。所以大学里没人喜欢理论信息学的东西。

      【讨论】:

      • 当然你可以为解释性语言构建编译器,但编译后的机器代码本身就是运行时的镜像。
      • 不仅仅是运行时的镜像。例如。想象一下许多脚本语言中的eval() 之类的构造:实际上,您必须在生成的程序中包含编译器,而不仅仅是运行时。
      【解决方案10】:

      The Python Book © 2015 Imagine Publishing Ltd,通过第 10 页中提到的以下提示简单地区分了差异:

      Python 等解释型语言是一种将源代码转换为机器代码,然后在每次程序运行时执行的语言。这与诸如 C 之类的编译语言不同,后者的源代码只转换为机器代码一次——然后每次程序运行时都会执行生成的机器代码。

      【讨论】:

        【解决方案11】:

        我猜这是计算机科学中最大的误解之一。 因为解释和编译完全是两个不同的东西,我们不能这样比较。

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

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

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

        • 机器级解释 - 这种解释发生在编译成机器代码的代码上。指令由处理器直接解释。诸如 C/C++ 之类的编程语言会生成可由处理器执行的机器代码。所以处理器可以直接执行这些指令。

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

        因此,归根结底,我们必须了解的是,世界上所有的编程语言都应该在某个时候被解释。处理器(硬件)或虚拟机。

        编译只是将我们编写的人类可理解的高级代码带入机器可理解的硬件/软件级别的过程。

        这是完全不同的两件事,我们无法比较。但是这个术语非常适合教初学者编程语言的工作原理。

        PS:
        一些编程语言(如 Java)采用混合方法来执行此操作。首先,将高级代码编译成虚拟机可读的字节码。在运行中,称为 JIT 编译器的组件将字节码编译为机器码。具体来说,多次重复执行的代码行被翻译成机器语言,这使得解释过程更快。因为硬件处理器总是比虚拟解释器/处理器快得多。

        How Java JIT compiler works

        【讨论】:

          【解决方案12】:

          编译是从用已编译的编程语言编写的代码创建可执行程序的过程。编译允许计算机运行和理解程序,而不需要用于创建它的编程软件。编译程序时,通常会针对与 IBM 兼容计算机兼容的特定平台(例如 IBM 平台)进行编译,但不适用于其他平台(例如 Apple 平台)。 第一个编译器是由 Grace Hopper 在哈佛 Mark I 计算机上开发的。今天,大多数高级语言都将包含它们自己的编译器或提供可用于编译程序的工具包。与 Java 一起使用的编译器的一个很好的例子是 Eclipse,与 C 和 C++ 一起使用的编译器的一个例子是 gcc 命令。根据程序的大小,编译应该需要几秒钟或几分钟,如果在编译时没有遇到错误,则会创建一个可执行文件。检查此信息

          【讨论】:

            【解决方案13】:

            简短(不精确)定义:

            编译语言:整个程序一次翻译成机器码,然后机器码由CPU运行。

            解释语言:程序是逐行读取的,只要读取一行,CPU就会执行该行的机器指令。

            但实际上,如今很少有语言是纯粹编译或纯粹解释的,它通常是混合的。更详细的图片说明,请看这个帖子:

            What is the difference between compilation and interpretation?

            或者我以后的博文:

            https://orangejuiceliberationfront.com/the-difference-between-compiler-and-interpreter/

            【讨论】:

              猜你喜欢
              • 2016-11-24
              • 1970-01-01
              • 2021-01-15
              • 1970-01-01
              • 2010-09-06
              • 2010-12-04
              • 2011-02-09
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多