編譯型語言與解釋型語言
首先要說明,編譯型語言與解釋型語言這種分類方法是不科學的,或者說已經過時了,但是這種稱呼大抵還是能夠讓人明白我們將要討論的是什么東西。
文中所列參考是筆者認為比較有幫助的一些擴展閱讀內容。
首先貼一個很形象的比喻,來自知乎@孛爾只斤南丁:
假設廠里來了兩個新工人,一個叫編譯,另一個叫解釋。廠長(程序員)給他們安排了一項任務(需求),并發放了操作說明(源代碼)。
編譯這名工人的做法是先完整的看一遍操作說明,遇到錯別字或者不明白的地方,就去問廠長,直到操作說明最終成為一個沒有錯別字且他自己完全能夠理解的東西。然后他把操作說明的內容理解消化(編譯),并且變成記憶(可執行程序)。之后每次需要完成任務的時候就靠自己的理解和記憶去執行,不再需要操作說明。
我們再看解釋這個工人。他拿到操作說明二話不說直接上手,讀一條,操作一步,再讀一條,操作下一步,如此重復。就算是操作說明中有錯別字或者他看不懂的地方,在沒有讀到那一條之前他是不知道的,也不影響他進行前面的操作,等真的讀到錯別字或者不能理解的條目再去問廠長。而且不管他執行這個任務執行了多少次,每次都是需要看著操作說明一步一步執行。
那這兩個工人誰好誰壞呢?難說。
如果給他們安排的任務以后要重復很多次,而且步驟繁多,但是相對穩定不需要頻繁調整,那么編譯工人的工作效率可能會更高一些。因為任務相對穩定不需要調整,所以他只要第一次把不明白的地方跟廠長問清楚,自己理解消化記住了,以后的執行都是他自己內化理解的東西,做起來很快。
如果任務步驟相對少呢,編譯工人其實也不會比解釋工人高效出多少。甚至可能解釋工人拿來就上手,編譯工人還沒讀完,人家已經操作完了。又或者任務不需要重復執行(如實驗代碼),那么對著操作說明直接干就是了,沒必要理解消化記憶。再者任務可能需要靈活性,每天需要根據廠長甚至是客戶的要求改來改去,解釋工人可能更加出色。每次改動,編譯工人還要重新看一遍完整的操作說明(當然了,我們可以把操作說明分章節,那他只會看更新的章節),但是解釋工人就不用這么麻煩,反正他都是讀一條操作一步,你改不改動影響不大。
傳統認知中的編譯型、解釋型、混合型
編譯型
編譯型:需通過編譯器(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種:
- 直接運行高級編程語言(如Shell內置的解釋器)
- 轉換高級編程語言碼到一些有效率的字節碼(Bytecode),并運行這些字節碼
- 以解釋器包含的編譯器對高級語言編譯,并指示處理器運行編譯后的程序(例如: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是一個棧結構虛擬機(這里虛擬機分為基于棧的和基于寄存器的),它把字節碼中的指令一條條執行過來就行。不用轉換字節碼。基于這個事實來講,可以認為解釋器和虛擬機的區別正在越來越小,已經是我中有你,你中有我的地步。獨立的分割來看,可能還能區分這幾步是解釋器行為,這幾步是虛擬機的行為,但是作為一個整體來看,兩者的區別確實沒那么明顯。