編譯器與解釋器的區別和工作原理
這篇教程,我們一起來了解編譯器和解釋器的工作原理,這對我們提升能力很重要。
我一直認為概念或理論知識是非常重要的,讓我們能夠將很多模糊的認知關聯起來,變得更加清楚,從而打開視野,上升到新的層次。
但是,一般來說,在剛剛入門的時候,接觸一些概念性、理論性的內容,不但非常枯燥,而且難以理解。
而在一定時間的學習接觸之後,再來看這些東西,則會變得更加容易領悟,理解的更透徹。
這篇教程會包含很多專業術語,我會對其中一些專業術語進行解釋,對於一些未做解釋的專業術語,建議大家通過搜索引擎進行理解。
首先,從Python這種編程語言說起。
它有以下幾個特點:
- 面向對象:在本站的《Python3萌新入門筆記》中有專門的文章,簡單來說是指在程序設計中能夠採用封裝、繼承、多態的設計方法。
- 動態語言:是在運行時可以改變其結構的語言;例如,在程序運行過程中,給一個類的對象添加原本不存在的屬性。
- 動態數據類型:變數不需要指定類型,但需要解釋器執行代碼時去辨別數據類型;這個特點讓編程變得簡單,但代碼執行效率變低。
- 高級語言:是指高度封裝了的編程語言,相對於機器語言,更加適合人類編寫與閱讀。
- 解釋型語言:是指無需編譯,直接能夠將源代碼解釋為機器語言進行運行的語言。
從最後一個特點,我們能夠看到Python是解釋型語言,也就是說源代碼需要通過解釋器進行解釋執行。
編程語言分為編譯型語言和解釋型語言,我們需要了解它們的區別,才能夠更好的理解編譯器和解釋器的區別。
相信大家都知道C和C++。
這兩種語言都是編譯型語言。
編譯型語言的特點是執行速度快,缺點是什麼呢?
編譯型語言需要編譯器處理,主要工作流程如下:
源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器 (Linker) → 可執行程序 (executables)
在這個工作流程中,編譯器調用預處理器進行相關處理,將源代碼進行優化轉換(包括清除注釋、宏定義、包含文件和條件編譯),然後,通過將經過預處理的源代碼編譯成目標代碼(二進位機器語言),再通過調用鏈接器外加庫文件(例如操作系統提供的API),從而形成可執行程序,讓機器能夠執行。
在這個工作流程中,目標代碼要和機器的CPU架構相匹配,庫文件要和操作系統相匹配。
如果想在不同CPU的機器或者系統上運行C語言的源代碼,就需要針對不同的CPU架構和操作系統進行編譯,這樣才能夠在機器上運行程序。
所以,編譯型語言的缺點我們就看到了,它不適合跨平台。
而且,到這裡大家應該能知道,為什麼CPU一樣,但是exe程序只能Windows中運行,而不能在Mac中運行了。
如果上面感覺不太好理解,我舉一個貼近生活的例子:
一名會多國語言的老師教了很多外國學生,這些學生分別來自有英國、美國、法國、德國、韓國。
當這名老師給這些學生髮放學習資料的時候,都需要把中文資料先進行翻譯,變成英文版、德文版、法文版和韓文版的電子文檔,再分別發給每個國家的學生去學習。
這個翻譯的工作非常繁瑣。
不僅,要翻譯成每個國家的語言,而且,還要考慮英式英語和美式英語的區別分別翻譯成不同的英文版。
再有,就是每次資料更新還都要重新翻譯一遍。
在上面的例子中,中國老師就像是編譯型語言的開發人員,中文資料就是編譯型語言的源代碼,翻譯後的資料就是不同CPU架構的機器語言,不同語言的學生就是不同CPU架構的機器,美國和英國的學生,就是CPU架構相同但操作系統不同的機器。
注意:這裡涉及到跨平台的概念,平台可以理解為不同CPU架構(例如X86、ARM等)的機器和同種CPU但不同的操作系統(例如Unix、Windows等)的機器。
提示:建議大家閱讀上述內容時,了解一下預處理器、鏈接器、庫文件(靜態鏈接庫和動態鏈接庫)的相關概念。
我們繼續看剛才的例子。
這位累得要死的中國老師,開始想辦法。
他想,我幹嘛自己去翻譯,給他們一人一個自動翻譯軟體不就好了?
於是,老師就給每個學生定製了一個自動翻譯軟體,這個軟體能夠一頁一頁的原始中文資料翻譯成不同的語言資料給學生看。
現在的情況,這名老師工作會輕鬆很多,不需要再考慮製作各種語言版本的資料,只需要把精力放在製作中文資料就好了。
早期的解釋器就是這樣的工作流程:源代碼 (source code) → 解釋器 (interpreter) 。
源代碼無需預先編譯成可執行程序。
在程序執行時,解釋器讀取一句源代碼之後,先進行詞法分析和語法分析,再將源代碼轉換為解釋器能夠執行的中間代碼(位元組碼),最後,由解釋器將中間代碼解釋為可執行的機器指令。
所以,編譯型語言的可執行程序產生的是直接執行機器指令,而解釋型語言的每一句源代碼都要經過解釋器解釋為可以執行的機器指令,相比之下解釋型語言的執行效率會低一些。
但是,解釋型語言在不同的平台有不同的解釋器,源代碼跨平台的目的實現了,開發人員不用再考慮每個平台如何去編譯,只需要關注代碼的編寫,編寫完的代碼在任何平台都能無需修改(或少量修改)就能正確執行。
例如,Linux系統中執行Python源代碼時支持 fork()函數,而window系統中不支持這個函數,如果將運行在Linux系統中的源代碼移植到Windows系統,這時就需要進行修改。
理解了編譯型語言和解釋型語言的區別,我們繼續看例子。
老師雖然給學生定製了翻譯軟體,但是發現這個軟體翻譯每頁內容都很慢,究其原因,這個軟體需要先把每頁內容分析一遍,將內涵複雜的中文(例如成語)轉換成簡單直接能夠直接進行翻譯的中文,再進行向其它國家語言的翻譯。
看到這個問題之後,軟體的提供商想出了一個解決方案。
這個方案就是:第一次打開資料時,讓翻譯軟體把原始資料完整的進行分析轉換,保存成一個能夠直接翻譯的中間文件;然後,翻譯程序再一頁一頁的讀取轉換後的中間文件去翻譯;這樣的話,雖然第一次打開時慢了些,但是,當學生再次打開資料時,只要原始資料沒有更新,就直接通過保存的中間文件進行翻譯,速度會有很大提升。當然,程序打開時,需要對比一下原始資料是否和中間文件一致,如果有修改,則再次編譯出新的中間文件,覆蓋舊的中間文件。
Python程序運行時,就像上面的例子一樣,先將源代碼完整的進行轉換,編譯成更有效率的位元組碼,保存成後綴為「.pyc」的位元組碼文件,然後,翻譯器再通過這個文件一句一句的翻譯為機器語言去執行。
注意:Shell中執行源代碼時不會生成中間文件,每次都是讀取源代碼,轉化為位元組碼後,解釋執行。
上面的例子還沒結束。
軟體供應商提出的方案雖然解決了一些效率問題,但是還無法完全讓人滿意。
經過苦思冥想,軟體供應商又想出了一個新的方案。
在原始資料中有很多重複的內容;
這些重複的內容如果翻譯一次之後,就把它保存,再碰到相同的內容就直接使用保存的翻譯結果。
而沒有必要每次都再翻譯。
長時間運行程序時,速度就會快上很多。
這個例子實際上就是JIT即時編譯器(Just-In-Time Compiler)的比喻。
無論是使用解釋器進行解釋執行,還是使用編譯器進行編譯後執行,最終源代碼都需要被轉換為對應平台的本地機器指令。
那麼,一些重複出現的代碼,就可以將其編譯為本地機器指令,重複使用,從而提高效率。
這些重複出現的代碼包括多次調用的方法和多次執行的循環體。
JIT即時編譯器比較典型的例子是在JVM(Java虛擬機)中。
Java程序最初是通過解釋器進行解釋執行的,當Java虛擬機發現某個方法或代碼塊運行特別頻繁的時候,就會認為這是「熱點代碼」(Hot Spot Code)。JIT即時編譯器會將這些「熱點代碼」編譯成與本地機器相關的機器指令,進行各個層次的優化。
當程序需要迅速啟動和執行的時候,解釋器可以首先發揮作用,省去編譯的時間,立即執行。在程序運行後,隨著時間的推移,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地機器指令之後,可以獲取更高的執行效率。當程序運行環境中內存資源限制較大,可以使用解釋器執行節約內存,反之可以使用編譯執行來提升效率。
大家都知道,Java程序的運行性能很高,基本上可以和C/C++的程序相媲美。這主要是因為JIT即時編譯器可以針對那些頻繁被調用的「熱點代碼」做出深度優化,而靜態編譯器無法完全推斷出哪些是運行時的熱點代碼,而不能做出針對性的優化。因此,通過JIT即時編譯器編譯的本地機器指令才會比直接生成的本地機器指令擁有更高的執行效率。
Python有多種解釋器,比較著名的有CPython、IPython、PyPy、Jython和IronPython等。
其中CPython是Python官方默認的解釋器,它是用C語言實現Pyhon解釋器。
CPython是單純的翻譯器,將源代碼轉化為位元組碼之後解釋執行。
而另外一款使用Python實現的Python解釋器PyPy,比CPython解釋器更加靈活。因為PyPy採用了JIT技術,在程序的運行性能上PyPy將近是CPython解釋器執行效率的1至5倍。
而其它的解釋器都各有特點。
IPython是基於CPython增強了交互。
Jython是運行在Java平台上的Python解釋器。
IronPython是運行在.Net平台上的Python解釋器。
以上就是對編譯器和解釋器區別和工作原理的講述,因為沒有找到現成的資料,所以,其中內容參考了很多網上的資料,多番查證後,進行了融合。如果有解釋錯誤,或者講解不透徹的地方,歡迎大家指正並提出建議。
推薦閱讀:
※入門 Lisp 有哪些在線資料?
※clojure中 x x #x 他們之間的關係一直很暈 能給一些應用場景例子嗎?
※沒學過編程的人直接學 Lisp 語言行嗎?
※lisp 性能怎麼樣?