编译型语言与解释型语言

7 篇文章 1 订阅
3 篇文章 0 订阅

编译型语言与解释型语言

首先要说明,编译型语言与解释型语言这种分类方法是不科学的,或者说已经过时了,但是这种称呼大抵还是能够让人明白我们将要讨论的是什么东西。

文中所列参考是笔者认为比较有帮助的一些扩展阅读内容。

首先贴一个很形象的比喻,来自知乎@孛尔只斤南丁

假设厂里来了两个新工人,一个叫编译,另一个叫解释。厂长(程序员)给他们安排了一项任务(需求),并发放了操作说明(源代码)。

编译这名工人的做法是先完整的看一遍操作说明,遇到错别字或者不明白的地方,就去问厂长,直到操作说明最终成为一个没有错别字且他自己完全能够理解的东西。然后他把操作说明的内容理解消化(编译),并且变成记忆(可执行程序)。之后每次需要完成任务的时候就靠自己的理解和记忆去执行,不再需要操作说明。

我们再看解释这个工人。他拿到操作说明二话不说直接上手,读一条,操作一步,再读一条,操作下一步,如此重复。就算是操作说明中有错别字或者他看不懂的地方,在没有读到那一条之前他是不知道的,也不影响他进行前面的操作,等真的读到错别字或者不能理解的条目再去问厂长。而且不管他执行这个任务执行了多少次,每次都是需要看着操作说明一步一步执行。

那这两个工人谁好谁坏呢?难说。

如果给他们安排的任务以后要重复很多次,而且步骤繁多,但是相对稳定不需要频繁调整,那么编译工人的工作效率可能会更高一些。因为任务相对稳定不需要调整,所以他只要第一次把不明白的地方跟厂长问清楚,自己理解消化记住了,以后的执行都是他自己内化理解的东西,做起来很快。

如果任务步骤相对少呢,编译工人其实也不会比解释工人高效出多少。甚至可能解释工人拿来就上手,编译工人还没读完,人家已经操作完了。又或者任务不需要重复执行(如实验代码),那么对着操作说明直接干就是了,没必要理解消化记忆。再者任务可能需要灵活性,每天需要根据厂长甚至是客户的要求改来改去,解释工人可能更加出色。每次改动,编译工人还要重新看一遍完整的操作说明(当然了,我们可以把操作说明分章节,那他只会看更新的章节),但是解释工人就不用这么麻烦,反正他都是读一条操作一步,你改不改动影响不大。

传统认知中的编译型、解释型、混合型

编译型

编译型:需通过编译器(compiler)将源代码编译成机器码,然后链接为可执行文件。这个过程对于在 Linux 下编译过代码的大家来说应该比较熟悉了,这里以 gcc 的工具链为例:

源代码(.c/.cpp) → 预处理(cpp) → 编译(cc1) → 汇编(as) → 链接(ld) → 可执行文件(.elf)

整个过程可参考:从C源代码到可执行文件的四个过程:预处理、编译、汇编、链接

  • 编译:把源代码编译成机器码;编译的过程又可分为:

    源代码 → 词法分析 → 语法分析→ 语义分析 → 中间代码生成 → 优化 → 目标代码生成 → 目标代码

    这就是大家熟悉的编译原理中学习过的的前中后端了。

  • 链接:把各个模块的机器码和依赖库串连起来生成可执行文件。链接也是有许多学问的,又可分为静态链接和动态链接。可参考:Linux下的ELF文件、链接、加载与库(含大量图文解析及例程)

主流实现为编译型的语言有:C、C++、Object-C、swift 等。值得一提的是,Java 很多时候也被分类为编译型,这正是我们说所谓编译型语言的说法过时的原因,我们会在后面详细讨论 Java。

解释型

解释性语言的程序不需要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。代表有 Python、JavaScript、PHP 等。

混合型

混合型:编译器将源码编译成中间码而不再是二进制机器码,然后中间码需要被即时编译器翻译成目标平台的本地代码。待表有:C#、Java。

编程语言及其实现

读者应该注意到上一小节我们的提法是编译型、解释型、混合型,而不是编译型语言、解释型语言、混合型语言

是这样的,即使有所谓的编译型、解释型之分,这种分类也是相对于语言的实现而言,而非语言本身。语言的本身只是规定了源代码的语法,至于怎样将源代码执行起来,这应当称为是语言的实现。那么对于语言的实现我们将其可以分为编译型、解释型和混合型

以下引自:计算机语言分类

将编程语言分类为编译型语言、解释型语言被认为是不科学的,因为很多语言既可以认为是解释型、也可以认为是编译型,这种分类方式被指出是不科学的,见于:RednaxelaFX虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 中提到的:我是倾向于避开把编程语言描述为“编译型”或者“解释性”的。

详细地,下面以 Java 和 Python 为例子来解释这个问题。

Java 是这样从源码到被执行的(大致地~):

Java 源代码 -> javac 将其转为字节码(二进制码)->虚拟机中执行。

Java 按这种分类方式难以分类的原因就如上所示,首先编译其次在虚拟机中解释执行。为何说后者是解释?因为传统上我们认为从字节码到对应平台的机器码需要不同平台上的 JVM 提供支持,我们认为这个动作就是解释。

这样一来 Java 就难以按照这个分类方式进行分类了。实际上,我还是倾向于将 Java 称之为编译型语言,因为完全可以将 JVM 看做底层实现。这里粗粒度不宜过细,因为本质上说机器码被 CPU 接收然后运行,其中也涉及一段解释的过程。如此一来,世上只有解释型语言。

Python 虽然被普遍认为一门解释型语言,按理说应当不涉及编译过程。事实上,Python 解释器会将源代码转换为字节码(.pyc),然后再由 Python 解释器来执行这些字节码。本质上,Python 解释器不就是完成了编译器+执行器这个模块的任务,既然含有编译过程,那么其被称为解释型语言就具有一定不合理性。

R 大所认为的:语言一般只会定义其抽象语义,而不会强制性要求采用某种实现方式。而编译、解释只是实现方式的一个步骤或者方式,按这种分类是不合理的。

Python解释器与Java虚拟机

Python、Java对比的例子经常被用来说明这个问题。

Python 和 Java 的执行过程中都有字节码(.py和.class)的概念,它们的字节码都是既可以编译执行也可以解释执行的,这取决于后续处理这些字节码的 VM 的实现。

  • Python 的 VM 或者说 Runtime 有多种实现,CPython,JPython,PyPy,ironpython等,尤其是 PyPy 中是支持 JIT 即时编译的,还有 ironpython 的机制几乎就和 .NET 平台上的其他语言一样了。这使得 Python 也很难被直接归类为所谓的 ”解释型语言“ 了。但是 Python 中字节码(.pyc)不是必须的,而是可以直接由 Python解释器对源代码解释执行。可参考博客:python解释器
  • Java 不同于 Python,它是一定要先编译一步拿到字节码 (.class) 的,这也是为什么 Java 常被分类为 “编译型语言”。但是在 JVM 拿到字节码之后,Java 的 JVM 的实现就很多样了,可以解释,可以编译,编译又可以分为 JIT 和 AOT。选择多多,细节多多。因此,Java 也可以被认为是混合型。可参考:Java一次编译,到处运行是如何实现的JIT(动态编译)和AOT(静态编译)编译技术比较

以下参考自:凭啥Java的运行环境称虚拟机,Python的只能称解释器

看到Stackoverflow上有个问题在讨论Java和Python的对比,其中就有人问答为啥Java的运行环境被称之为JVM,而Python的只能叫做Interpreter。

这个问题估计想过的人不多,先找维基百科看一下虚拟机的定义。

虚拟机的定义有2个,一种是类似Vmware的系统虚拟机,另一种是虚拟机称之为程序虚拟机,诸如JVM,CLR就是最常见到的虚拟机。

程序虚拟机也称作托管运行时环境,运行这个虚拟机时,就好比普通的OS中的一个进程。当这个进程启动时,虚拟机启动,当进程销毁时,虚拟机销毁。使用虚拟机的目的就是提供一个和平台无关的编程环境。

JVM中的执行引擎只能处理编译后的Java字节码,字节码处理引擎其实包含一个字节码解释器和一个JIT编译器(和.net的CLR中JIT差别很大),解释器逐条的执行字节码指令,速度稍慢。JIT编译器则会将热点代码编译缓存起来,因此执行速度加快。

解释器的概念比较简单,它可以将代码翻译,并运行,不需要经过编译,JVM中的解释器正式这样的,JVM中解释的就是字节码。解释器运行程序的方法有3种:

  1. 直接运行高级编程语言(如Shell内置的解释器)
  2. 转换高级编程语言码到一些有效率的字节码(Bytecode),并运行这些字节码
  3. 以解释器包含的编译器对高级语言编译,并指示处理器运行编译后的程序(例如:JIT)

其中Python的解释器就是属于第二种,Python代码在首次运行时,它会将Python代码编译成字节码,如果可以的话,它会将这个字节码保存到**.pyc文件**中,这样下次启动的时候就不会再编译这些代码而是直接解释运行字节码。事实上,这种机制正在模糊解释器和编译器之间的界限,或者说是模糊了解释型语言和编译型语言的界限。

通过JVM和解释器的概念澄清,似乎还是不明白为啥JVM就被称为虚拟机,JVM中有运行的是字节码,它可能直接被解释执行,也可能被再次编译成目标语言,Python中的解释器也会先预编译Python代码为字节码,再解释执行。那么到底有啥区别?

很多人参与了讨论,分别从不同的角度去阐述区别。

有人认为虚拟机是和语言无关的,JVM为例,除了Java之外,Scala,Clojure,甚至Python借助于Jython工具,也可以运行在JVM上,而没听说什么语言能有Python解释器解释执行,除了Python。

也有人从语言的类型上,Java为静态类型的语言,而Python为动态语言。这使得Java字节码既可以被解释执行也可以被编译成机器指令再执行。而Python则复杂多了,它虽然让程序员可以不去关注变量的类型,但解释器不得不去推断数据类型,这一定程度上影响性能。

还有观点认为解释器是一个历史遗留术语,现代语言中虚拟机和解释器的分界已经很模糊甚至不存在。

事实上,在《Learning Python》一书中,作者把Python的解释器称为PVM。PVM是一个栈结构虚拟机(这里虚拟机分为基于栈的和基于寄存器的),它把字节码中的指令一条条执行过来就行。不用转换字节码。基于这个事实来讲,可以认为解释器和虚拟机的区别正在越来越小,已经是我中有你,你中有我的地步。独立的分割来看,可能还能区分这几步是解释器行为,这几步是虚拟机的行为,但是作为一个整体来看,两者的区别确实没那么明显。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值