免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
大白話聊聊編譯那點事兒

作為程序員的我們,每天寫各種語言的各種代碼,點一下IDE環(huán)境里的run,或者用一行命令一跑,一個程序就運行起來了。我們寫好的那一行行代碼,其實就是最普通的文本字符串,這些個文本字符串是怎么變成一個個漂亮的界面,一個個大數(shù)據(jù)量吞吐的服務(wù)器,一個個聰明的人工智能AI的?這里面其實經(jīng)歷了三個過程,編譯,鏈接,裝載(腳本語言會特殊一些,本文后面也會提及)

  • 編譯:編譯系統(tǒng)會讀取我們寫的文本字符串,去解讀這里面代碼所蘊含的意義,解讀出來后會翻譯成機器能看懂的匯編語言,我們管這個結(jié)果叫目標(biāo)文件,這個過程叫編譯

  • 鏈接:每個類,每個文件都會被編譯成不同的目標(biāo)文件,鏈接器把這每個目標(biāo)文件穿起來,讓他們之間能夠相互調(diào)用,最后生成可執(zhí)行文件,這個過程叫鏈接

  • 裝載:把已經(jīng)生成的可執(zhí)行文件放到操作系統(tǒng)里,在系統(tǒng)專屬的進程與內(nèi)存控制下,找到機器可以識別的匯編代碼入口,開始按著匯編去執(zhí)行機器碼
    ,并且能與操作系統(tǒng)級別的各種系統(tǒng)Api對接起來,這個過程叫裝載

這一篇主要聊聊編譯,但我會持續(xù)把大白話聊鏈接,裝載堅持寫完

編譯步驟

怎么能讓編譯系統(tǒng)理解你寫出來的一行行字符串,讓機器去讀懂你寫的代碼?這里面其實經(jīng)歷了很多步驟,我之前從antlr扯淡到一點點編譯原理里提到過一點點,這里我們再扯一扯~

  • 預(yù)編譯

  • 詞法分析

  • 語法分析

  • 語義分析

  • 生成抽象語法樹

  • 生成中間碼

  • 目標(biāo)代碼生成

  • 目標(biāo)代碼優(yōu)化

預(yù)編譯

在預(yù)編譯的過程中,會處理源代碼中的那些以#開頭的預(yù)編譯指令,比如#include,#define,#ifdef等,在編譯開始前就先對原始的代碼文件進行調(diào)整,經(jīng)過了預(yù)編譯之后,你寫的代碼其實已經(jīng)改變了很多。不能說面目全非,但也變化很大,不再是你親手寫出來的樣子了

  • #define大家都知道是宏定義,在預(yù)編譯環(huán)節(jié)會掃描所有的宏,并將宏展開成真正的原始代碼

  • #include大家知道是引用的意思,但是引用在預(yù)編譯階段是怎么操作的呢?其實這行預(yù)編譯指令就是原封不動的把.h文件插入到寫include的位置,既然是原封不動的copy插入,這里會涉及一些命名重定義問題,這就是為什么有時候?qū)懺?h里面的定義需要加static關(guān)鍵字。

  • #indef大家都知道是條件編譯,通過上面講的幾種預(yù)編譯的實際操作過程,也能猜到其實就是字符層面的刪減,只有符合條件編譯的情況,這里面的代碼才會被保留,如果編譯條件為false,在輸入編譯器之前,你寫的那大段代碼就已經(jīng)被刪掉丟棄了

  • /* */ & //刪除注釋,是滴,注釋是對編譯完全沒用的,因此注釋是不會參與編譯的

  • #pragma是一種編譯器指令,這種編譯器指令會被保留,后續(xù)編譯有用

  • 添加行號,給每行代碼添加行號,萬一編譯報錯,也方便追查

看到宏的操作我們可以理解到,為什么我們不推薦把一些業(yè)務(wù)中常用的常量用宏來表示,如果一個宏被修改,那么相當(dāng)于所有用宏的地方,你寫出的代碼都變了,要重新編譯

如果一個宏被寫入了.h文件,這個.h文件又被include到各種地方,甚至寫入了pch,那么一個宏修改,不管你用不用這個宏,受影響的所有文件都等同于你直接修改了代碼,要重新編譯

詞法分析

詞法分析其實是用一個掃描器逐行去掃描整個代碼文件,通過一些算法(有限狀態(tài)機算法)把你寫的字符串分割成一個個的記號token,你寫的關(guān)鍵字,標(biāo)識符,字面量,運算符號等,都會被詞法分析一一識別,分割,然后按順序排列成一個個的標(biāo)記。

  • 關(guān)鍵詞:比如for while if static等會被詞法分析識別成單詞

  • 標(biāo)識符:你代碼起名的常量名字,變量名字等

  • 字面量:你在代碼里寫死的一些值,數(shù)字,字符等,比如@“1”

  • 特殊符號: - * /等等

讓你的代碼不再是一個char 一個char相互之間無關(guān)聯(lián)的字符串,而變成了一個標(biāo)記一個標(biāo)記的標(biāo)記流(你可以理解有獨立意義的單詞),每一個標(biāo)記可能是個值,可能是個變量名字,可能是個運算符,可能是個語法單詞。

在詞法分析階段,掃描器是并不清楚這一個個標(biāo)記是什么意思的,他不需要知道for代表循環(huán),int代表整形,他只需要知道for,int,是一個個獨立的單詞。

其實掃描器在經(jīng)歷過詞法分析后,會簡單的對這些token進行歸類,符號,常量等可以簡單處理的會放入不同的常量區(qū)符號區(qū)。

語法分析

語法分析就是真正的編譯器在嘗試讀懂你寫的代碼了,經(jīng)過了詞法分析你得到的token流,會按順序輸入語法分析器,語法分析器會嘗試解讀,最終將我們希望表達的自然語義,構(gòu)建成了一個邏輯上的計算機能識別,能執(zhí)行,能遍歷的結(jié)構(gòu)–樹狀結(jié)構(gòu)。也就是抽象語法樹(Abstract Syntax Tree)

每一條語句都是一個表達式,復(fù)雜語句就是復(fù)雜表達式的組合,可以相互嵌套,語法分析會把token流中很明顯的表達式token識別出來,識別出表達式的核心意圖,識別出表達式的參數(shù),同時語法分析器會按著自己內(nèi)部的運算符優(yōu)先級規(guī)則,去調(diào)整表達式的執(zhí)行順序。

  • 遇到了=token,語法分析器會知道這是一個賦值表達式,左邊的token是賦值對象的表達式 or token,右邊是值的表達式 or token

  • 遇到了 token,語法分析器會知道這是一個加法表達式

  • 按著語法分析器的內(nèi)置規(guī)則,把一個又一個的表達式,按著語義去組合去嵌套,組合成一堆表達式的樹狀結(jié)構(gòu)

如果此時我們寫代碼不嚴謹,哪里少了個括號,哪里賦值沒寫值就直接回車,這時候語法樹都是無法生成的,就會直接編譯報錯。

大學(xué)的時候計算機有一門課程作業(yè)就是自己實現(xiàn)一個計算器,要求可以用字符串連續(xù)輸入一串?dāng)?shù)學(xué)運算式,由我們自己用算法來處理一級運算符 - 與二級運算符 * / 甚至還要處理括號,最終得出計算器的最終結(jié)果。

這個處理過程很像,我們會識別出一行字符串里,優(yōu)先計算出 * / 二級運算符的結(jié)果,用結(jié)果再去計算 - ,最后得出了一個樹按著樹去深度遍歷,就能拿到計算結(jié)果

語義分析

我們已經(jīng)初步得到了抽象語法樹,這個抽象語法樹每個節(jié)點都是一個表達式,但是這個表達式是否有意義,此時還并不確定,一個賦值表達式,我不能將一個數(shù)值賦值給一個對象指針,一個乘法表達式我不可能把一個地址指針與一個數(shù)值相乘。

語義分析就是進行這種靜態(tài)分析,會遍歷整個語法樹,把每個節(jié)點的表達式都標(biāo)識類型,并且驗證是否合法,

抽象語法樹

從語法分析開始,就提到生成抽象語法樹(Abstract Syntax Tree),經(jīng)過了語義分析,AST又變的更加完善,自此最終版的抽象語法樹已經(jīng)建立完成

生成中間碼

不同硬件平臺的匯編處理都是不一樣的,這和硬件,CPU,總線的設(shè)計都有關(guān)系,一個AST如果想要在各個平臺都能運行,那么就得生成很多個平臺的匯編碼。

計算機科學(xué)領(lǐng)域的任何問題都可以通過增加一個間接的中間層來解決

于是我們在AST與多個平臺的匯編代碼中間,抽象出了一個中間碼(Intermediate Representation),他與語言無關(guān)(過了AST之后,就和語言無關(guān)了),與平臺無關(guān)(他并沒有直接生成匯編,在中間碼的設(shè)計里打平了平臺差異硬件差異)。

最后通過目標(biāo)平臺的匯編器,由中間碼生成匯編

目標(biāo)代碼

計算機是通過匯編來執(zhí)行操作的,機器能夠聽從并執(zhí)行諸如,移動到內(nèi)存某個地址,位移多少個字節(jié),寫入多少byte的數(shù)據(jù),這種匯編指令,因此一個程序如果想讓硬件機器能夠執(zhí)行,最終一定是通過這樣的匯編指令來實現(xiàn)。

我們手頭有了抽象語法樹,他是一個樹狀的結(jié)構(gòu),每個節(jié)點都是一個表達式的描述,但這畢竟不是機器能讀懂的匯編,因此我們需要遍歷這個AST,把AST的每一個表達式,每一層邏輯,都先轉(zhuǎn)化成上面提到的中間碼,在根據(jù)不同的硬件平臺,翻譯成機器可以讀懂的語言–匯編,最終翻譯成的匯編語言文件,就是目標(biāo)文件(以后的篇幅講鏈接會重點介紹)

這一部分還可以細分為

  • 目標(biāo)代碼生成

  • 目標(biāo)代碼優(yōu)化

編譯前端-編譯后端

我們介紹了七個環(huán)節(jié),預(yù)編譯,詞法分析,語法分析,語義分析,抽象語法樹,中間碼生成,目標(biāo)代碼生成,目標(biāo)代碼優(yōu)化,我們把中間碼生成當(dāng)做一個分界線,前邊的環(huán)節(jié)就叫編譯前端,后面的環(huán)節(jié)叫做編譯后端

這么區(qū)分有啥好處?這樣的設(shè)計就保證了中間碼這個東西,語言無關(guān)&平臺無關(guān)。

編譯前端

預(yù)編譯,詞法分析,語法分析,語義分析,抽象語法樹,這些環(huán)節(jié)共同組成了編譯前端,編譯前端專門去處理語言專屬的特性。不同的語言他的詞法關(guān)鍵字,他的語法規(guī)則,語義分析的函數(shù)類型校驗,都是不同的,甚至不是所有語言都有預(yù)編譯這個環(huán)節(jié),但每個語言可以開發(fā)一個屬于自己語言的編譯前端,只要生成統(tǒng)一的標(biāo)準的中間碼,就可以無縫對接給任意編譯后端,這就是語言無關(guān)

編譯后端

目標(biāo)代碼生成,目標(biāo)代碼優(yōu)化,這些環(huán)節(jié)共同組成了編譯后端,其實編譯后端還會有本文沒有深入講的鏈接環(huán)節(jié)(將目標(biāo)文件串聯(lián)成可執(zhí)行文件),編譯后端專門負責(zé)處理各個平臺的差異,根據(jù)不同平臺,編譯后端進行不同的匯編代碼生成,無論是什么語言生成的標(biāo)準中間碼,只要編譯后端支持的硬件平臺,通過編譯后端都能直接生成對應(yīng)的目標(biāo)文件,編譯后端不支持的硬件平臺?擴展編譯器,讓編譯器支持一下咯╮(╯_╰)╭,這就是平臺無關(guān)

編譯工具

GCC

GCC(GNU Compiler Collection,GNU編譯器套裝),既然是編譯器套裝,那么其實GCC內(nèi)部包含了編譯前端與編譯后端所有模塊。

GCC的編譯前端部分原本只支持C,后來很快就擴展支持了C ,再到后來,GCC也擴展支持了很多包括Fortran、Pascal、Objective-C、Java。

GCC的編譯后端也是很強大的,移植各個平臺都支持,包括x86、mips、Alpha、ARM、AVR、IA-64、SPARC、PowerPC等30多種平臺

GCC雖然在被廣泛的使用,但目前也面臨了危機,后起之秀CLang/LLVM,大有全面趕超GCC之勢頭。

Clang/LLVM

先說LLVM吧,以下內(nèi)容摘抄自百科

LLVM 命名最早源自于底層虛擬機(Low Level Virtual Machine)的縮寫,由于命名帶來的混亂,目前LLVM就是該項目的全稱。LLVM 核心庫提供了與編譯器相關(guān)的支持,可以作為多種語言編譯器的后臺來使用。能夠進行程序語言的編譯期優(yōu)化、鏈接優(yōu)化、在線編譯優(yōu)化、代碼生成。LLVM的項目是一個模塊化和可重復(fù)使用的編譯器和工具技術(shù)的集合

扯點歷史原因,最早蘋果也是用GCC進行一整套的編譯鏈接的,但據(jù)說蘋果對Objective-C語言打算加入很多新特性,但GCC開發(fā)者并不是很買賬,一度導(dǎo)致蘋果用的GCC版本與GCC主版本的分支割裂。隨著2005年Chris大神加入蘋果,蘋果決定徹底放棄GCC作為編譯后端,采用了全新的,高效的、模塊化的、協(xié)議更放松的LLVM作為編譯后端,蘋果處于一個GCC編譯前端/LLVM編譯后端的狀態(tài)。

并且GCC/LLVM的使用起來依然無法滿足蘋果的需求,甚至在蘋果的GCC分支版本擴展都無法滿足蘋果的要求,于是蘋果干脆從零去開發(fā)一個編譯前端Clang,目的就是干掉越來越用的不順手的GCC

于是形成了蘋果現(xiàn)在的Clang/LLVM的編譯前端 編譯后端的編譯體系。

有一種ClangPlugin的插件開發(fā)模式

可以支持在Clang編譯出AST的時候,開發(fā)輔助插件去干預(yù)或者處理Clang生成的AST

比如一些OCLint這種,OC編碼規(guī)范靜態(tài)檢查

比如直接把OC的AST轉(zhuǎn)化成JS代碼甚至類JSPatch代碼(想到了什么?滴滴的DynamicCocoa)

一些關(guān)于ClangPlugin開發(fā)的介紹文章

一些開源社區(qū)很火的Clang轉(zhuǎn)JS的項目

LLVM的發(fā)展

隨著LLVM的發(fā)展,他模塊化、高效的設(shè)計收到越來越多的組織青睞,不只是蘋果,越來越多的語言,都開始選擇用LLVM當(dāng)做編譯后端

如果你要開發(fā)一種新的編程語言,在詞法語法解析完成后,你要做什么,肯定是生成中間代碼,然后優(yōu)化,最后編譯成目標(biāo)機器碼。但是llvm 的中間代碼不僅效率高而且可讀性很好。那我們就直接拿LLVM過來用就好了,按照你的AST語法樹,利用llvm給你的操作IR的接口,生成等價的IR中間碼,生成IR了,之后所有的事情就交給llvm吧。

編譯器發(fā)展故事一則:

看到了一條微博上面講了一個故事

五十年代美國女程序員 grace hopper 發(fā)明第一個 compiler 之后,遭到頑固抵觸,很多程序員情愿費時費力的把程序用人工翻譯成機器代碼,也不愿用她的發(fā)明。早期的機器代碼就是像 A4 83 E7 C5 這類如看天書般的東西,使用 compiler 編譯器后工作效率提高幾十倍。但是一直到五十年代中期很多程序猿對編譯器仍然強烈抵觸。

當(dāng)時程序猿的主流觀點是,“讓一個機械的進程,去完成編譯高效代碼這樣一個偉大的工作,顯然是個愚蠢和傲慢的白日夢”.

今天許多科學(xué)家和工程師,看輕 Ai 和自動駕駛技術(shù)部署實施的速度,是不是在犯同樣的錯誤?

先不說后面對人工智能的評論,在計算機的遠古時代,程序員們還在人工去寫機器碼,這簡直是一個不敢想象的可怕的事情,而現(xiàn)在編譯器已經(jīng)發(fā)展到,我們程序員完全不需要掌握如此底層的知識就能讓各種各樣的程序運行起來,改變我們的世界。

虛擬機體系

那么Java呢?Java是這么操作的么?大家都知道Java有虛擬機JVM。

那么JavaScript呢?大家都知道腳本都是輸入腳本引擎去run的,js有jscore or V8引擎。

他們還是遵循一樣的流程嗎?

Java

我們上面提到過,其實GCC后來也支持編譯java,也就是說我們寫的java代碼也是要經(jīng)過編譯前端的全部流程,只不過到中間碼這一步產(chǎn)生了分歧

  • C系的編譯語言,生成中間碼后,最終目標(biāo)是生成匯編,也就是可執(zhí)行文件

  • Java的代碼經(jīng)過編譯前端后,會生成一種和中間碼類似概念的字節(jié)碼(ByteCode)

我們在前面建立過一個認知,機器能夠識別能夠執(zhí)行的代碼,是匯編那種的可執(zhí)行文件,中間碼還是字節(jié)碼這種東西,我們的程序認識能夠識別,能夠遍歷,但是機器是不認識的。

所以Java需要一個Java Runtime Envirnment,這里面就有JVM,Java運行環(huán)境就是可以識別這種字節(jié)碼的運行環(huán)境,如果一個設(shè)備,內(nèi)部安裝好了Java Runtime Envirnment,他不需要通過匯編來執(zhí)行代碼,Java運行環(huán)境可以直接將字節(jié)碼輸入,然后在java自己的虛擬機JVM里來運行。

所以Java是這樣一個編譯流程

  • 詞法分析

  • 語法分析

  • 生成AST

  • 生成字節(jié)碼(這個東西其實對應(yīng)的就類似LLVM中的IR中間碼)

這就完成了Java程序的編譯過程,我們就得到了字節(jié)碼這個結(jié)果,就是jar包里面的內(nèi)容。

Java的運行流程

  • 目標(biāo)設(shè)備必須具備 Java Runtime Envirnment

  • 通過Java運行環(huán)境來執(zhí)行字節(jié)碼

JavaScript腳本語言

都說js是腳本語言,不需要編譯,是完全解釋執(zhí)行的,真的是這樣嗎?

js也是需要進行編譯的,但是使用的不同引擎,可能內(nèi)部的執(zhí)行流程完全不一樣,拿JavaScriptCore來舉例。

js代碼會直接在運行的時候輸入給JSCore,JSCore也會進行如下的步驟

  • 預(yù)處理

  • 詞法分析

  • 語法分析

  • 生成語法樹

  • 生成字節(jié)碼

  • 用LLInt(Low Level Interpreter 解釋器)執(zhí)行字節(jié)碼

  • 更低級別的JIT執(zhí)行(好像還有2種,在運行負擔(dān)變大的時候會用更厲害的JIT去執(zhí)行)

看了這些怎么感覺和Java那個流程差不多?。繛槭裁催@玩意叫解釋性語言?

我覺得這里面有一個本質(zhì)性區(qū)別,就是同一個字節(jié)碼到底在什么時候生成?

JAVA的機制是,一次編譯生成后,字節(jié)碼可以每次運行的時候直接使用

JavaScript的機制是,每次運行的時候,再進行編譯生成字節(jié)碼,然后執(zhí)行,下次運行,又要重新編譯生成字節(jié)碼,重新執(zhí)行。

Web Assembly

令人激動的時候來了,web上的js每次運行都得實時編譯運行,就不能像Java那樣直接下發(fā)編好的字節(jié)碼,一次編譯,N次執(zhí)行呢?

這個腦洞就是目前炙手可熱的Web Assembly,WebAssembly是一種新的字節(jié)碼格式。它的縮寫是”.wasm”, .wasm 為文件名后綴,是一種新的底層安全的二進制語法。它被定義為“精簡、加載時間短的格式和執(zhí)行模型”

各大瀏覽器廠商紛紛跟進,也就是說,直接在瀏覽器里請求編譯好的wasm字節(jié)碼,就可以直接執(zhí)行,不必再每次運行的時候像javascriptcore一樣,每次都要編譯一次。

根據(jù)我們本文建立的編譯前端的認知,我們其實可以把任何語言(比如C )經(jīng)過詞法/語法/語法樹后生成wasm格式的字節(jié)碼,換句話說,用C 開發(fā)web也不是不可能~

其實在Web里運行C/C 也并不是一定只有最新的WebAssembly,那個是最新的高運行效率的一套新標(biāo)準

通過詞法分析,語法分析,語法樹,字節(jié)碼,解釋器的JavaScriptCore工作流程,我們可以重新設(shè)計

把面向JS語法的詞法分析,語法分析,替換成C的語法

這樣不就實現(xiàn)了一個C/C 的虛擬機了

已經(jīng)有開源項目這么做了

Github JSCPP項目

C-SMILE 一套支持C/C JS JAVA四種語言的scripting language

除此之外,這種自己定制一套字節(jié)碼,自己實現(xiàn)一套可以執(zhí)行字節(jié)碼的虛擬機,讓你想到了什么?騰訊的OCS

參考文獻

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
深入淺出讓你理解什么是LLVM
Swift 進階(一)基礎(chǔ)語法
開發(fā)者應(yīng)知道的編譯原理和語言基礎(chǔ)知識
c 各種編譯器(gcc clang)
自己動手開發(fā)編譯器(一)編譯器的模塊化工程
Java代碼在我們計算機上是如何運行的?
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服