《大前端開發(fā)者需要了解的基礎(chǔ)編譯原理和語言知識(shí)》讀后感

這幾天看了一篇名為《大前端開發(fā)者需要了解的基礎(chǔ)編譯原理和語言知識(shí)》,讀完之后深感作者知識(shí)面之廣,內(nèi)容之豐富,感謝作者分享如此高質(zhì)量的文章。

同時(shí),鑒于文章內(nèi)容較多,自己寫下這篇文章,作為心得和筆記。

代碼的編譯過程往粗了說分為四個(gè)階段:1.預(yù)處理(preprocessing)2.編譯(compliation)3.匯編(assembly)4.鏈接(linking)。

往細(xì)了說分為七個(gè)階段:1.預(yù)處理、2.詞法分析、3.語法分析、4.生成中間代碼、5.生成目標(biāo)代碼、6.匯編、7.鏈接。

這里面的主要區(qū)別就是編譯包括了詞法分析、語法分析、生成中間代碼和生成目標(biāo)代碼四個(gè)部分。

編譯器負(fù)責(zé)預(yù)處理、詞法分析、語法分析、生成中間代碼和生成目標(biāo)代碼五個(gè)步驟,編譯器的輸入是源代碼,輸出是中間代碼。編譯器以中間代碼為分界又分為編譯器前端編譯器后端編譯器前端負(fù)責(zé)語法分析生成抽象語法樹(AST,Abstract Syntax Tree),后端負(fù)責(zé)將抽象語法樹轉(zhuǎn)換為中間代碼。
ps:中間代碼已經(jīng)非常接近于實(shí)際的匯編代碼,它幾乎可以直接被轉(zhuǎn)化。主要的工作量在于兼容各種 CPU 以及填寫模板。此工作由編譯器或匯編器完成。

主流的C/C++的編譯器有GCC,這是GNU發(fā)布的一款極具影響力的編譯器,已經(jīng)成為LinuxUnix系統(tǒng)的默認(rèn)編譯器。從事iOS開發(fā)使用的Xcode目前用的編譯器是clang+LLVM。Xcode4之前,用的是GCC編譯器,由于GCC對(duì)Objective-C的支持不是很好,于是蘋果采用了“自家”發(fā)起的clang+LLVM作為默認(rèn)編譯器。

這兩個(gè)編譯器的主要區(qū)別是GCC直接負(fù)責(zé)整個(gè)編譯過程的五個(gè)步驟,而clang+LLVM將編譯的步驟拆分,clang作為編譯器前端還記得編譯器前端的職責(zé)么?),LLVM作為編譯器的后端兩者配合使用。當(dāng)然歷時(shí)上也曾經(jīng)出現(xiàn)過,GCC作為編譯器前端,LLVM作為編譯器后端的搭配組合。

Xcode 3和Xcode 4時(shí)代曾經(jīng)出現(xiàn)的GCC+LLVM搭配

接下來就以細(xì)分的模塊為例,總結(jié)一下每個(gè)模塊由什么負(fù)責(zé),分別都做了哪些工作。

1.預(yù)處理(preprocessing) —— 處理宏定義

預(yù)處理主要是處理一些宏定義,比如:#define、#include、**#if **等。預(yù)處理的實(shí)現(xiàn)有很多種,有的編譯器會(huì)在詞法分析前先進(jìn)行預(yù)處理,替換掉所有 # 開頭的宏,而有的編譯器則是在詞法分析的過程中進(jìn)行預(yù)處理。當(dāng)分析到 # 開頭的單詞時(shí)才進(jìn)行替換。雖然先預(yù)處理再詞法分析比較符合直覺,但在實(shí)際使用中,GCC 使用的卻是一邊詞法分析,一邊預(yù)處理的方案。(以上這些內(nèi)容是我從《大前端開發(fā)者需要了解的基礎(chǔ)編譯原理和語言知識(shí)》直接粘貼過來的)

2.詞法分析 —— 輸出符號(hào)狀態(tài)

詞法分析的主要實(shí)現(xiàn)原理是狀態(tài)機(jī),它逐個(gè)讀取字符,然后根據(jù)讀到的字符的特點(diǎn)轉(zhuǎn)換狀態(tài)。計(jì)算機(jī)不像人類可以直接識(shí)別源代碼的內(nèi)容,它只能一個(gè)一個(gè)的識(shí)別每個(gè)單詞,詞法分析要做的就是把源代碼分割開,形成若干個(gè)單詞,并標(biāo)記狀態(tài)為語法分析做準(zhǔn)備。比如,a=1”“a==1”,這兩個(gè)語句,計(jì)算機(jī)在從左向右識(shí)別時(shí),識(shí)別到第一個(gè) “=” 時(shí)并不能判定該語句是賦值符號(hào)還是條件判斷符號(hào),必須結(jié)合 “=” 之后的字符才能做出正確的判斷,若是 “1” 則是賦值符號(hào),若是 “=” 則是條件判斷符號(hào),根據(jù)識(shí)別的不同結(jié)果,進(jìn)行狀態(tài)標(biāo)記。具體的案例在原文中講的很詳細(xì),各位讀者可以參考。

詞法分析.png

3.語法分析 —— 輸出抽象語法樹(AST, Abstract Syntax Tree)

詞法分析以后,編譯器已經(jīng)知道了每個(gè)單詞的意思,但這些單詞組合起來表示的語法還不清楚,這時(shí)需要編譯器前端(如:GCC或者clang)進(jìn)行語法分析。

實(shí)現(xiàn)語法分析的一個(gè)簡單的思路是模板匹配,即將編程語言的基本語法規(guī)則抽象成模板進(jìn)行匹配,符合相應(yīng)的模板,即對(duì)應(yīng)相應(yīng)的意思,同時(shí)生成抽象語法樹(AST, Abstract Syntax Tree)。

比如有 “int a = 10;” 語句,其實(shí)表示了這么一種通用的語法格式:
類型 變量名 = 常量;”。

而在成功解析語法以后,我們會(huì)得到抽象語法樹(AST: Abstract Syntax Tree)。

語法分析.png

以這段代碼為例:

int fun(int a, int b) {
int c = 0;
c = a + b;

return c;

}

它的語法樹如下:

抽象語法樹.jpg

語法樹將字符串格式的源代碼轉(zhuǎn)化為樹狀的數(shù)據(jù)結(jié)構(gòu),更容易被計(jì)算機(jī)理解和處理。

4.生成中間代碼IR(Intermeidate Representation)

其實(shí)抽象語法樹可以直接轉(zhuǎn)化為目標(biāo)代碼(匯編代碼)。然而,不同的 CPU 的匯編語法并不一致,比如《AT&T與Intel匯編風(fēng)格比較》這篇文章所提到的,Intel 架構(gòu)AT&T 架構(gòu)的匯編碼中,源操作數(shù)目標(biāo)操作數(shù)位置恰好相反。Intel 架構(gòu)操作數(shù)立即數(shù)沒有前綴但** AT&T 架構(gòu)有。因此一種比較高效的做法是先生成語言無關(guān),CPU 也無關(guān)的中間代碼(IR)**,然后再生成對(duì)應(yīng)各個(gè) **CPU **的匯編代碼。

生成中間代碼(IR,Intermediate Representation)是非常重要的一步,一方面它和語言無關(guān),也和 CPU 與具體實(shí)現(xiàn)無關(guān)??梢岳斫鉃橹虚g代碼是一種非常抽象,又非常普適的代碼。它客觀中立的描述了代碼要做的事情,如果用中文、英文來分別表示** C Java 的話,中間碼某種意義上可以被理解為世界語**。

另一方面,中間碼編譯器前端后端的分界線。編譯器前端負(fù)責(zé)把源碼轉(zhuǎn)換成中間代碼(IR)編譯器后端負(fù)責(zé)把中間碼轉(zhuǎn)換成目標(biāo)代碼(匯編代碼)。
抽象語法樹生成中間碼(IR)的過程大體上分為兩步,第一步是生成中間碼(IR),第二步是將中間碼(IR)最佳化。

抽象語法樹轉(zhuǎn)中間碼.png

中間碼最佳化.png

以** GCC** 為例,生成中間代碼可以分為三個(gè)步驟:(
1.語法樹轉(zhuǎn)高端 gimple
2.高端 gimple 轉(zhuǎn)低端 gimple
3.低端 gimple 經(jīng)過 cfa 轉(zhuǎn) ssa 再轉(zhuǎn)中間代碼

注:使用Xcode 進(jìn)行iOS 客戶端開發(fā)時(shí),LLVM 負(fù)責(zé)將抽象語法樹轉(zhuǎn)為中間碼(IR)。

** Gimple** 是GCC編譯器產(chǎn)生的一種包含最多三個(gè)操作數(shù)的中間指令,也就是編譯原理里講的四元碼(三個(gè)操作數(shù),一個(gè)操作符),基本上也就是 dst = src1 @ src2 的這種形式。由于** Gimple** 最多只能對(duì)兩個(gè)操作數(shù)進(jìn)行計(jì)算,因此一個(gè)復(fù)雜的表達(dá)式會(huì)展開為一系列的** Gimple** 指令,這一過程就是** Gimple** 化。

4.1 語法樹轉(zhuǎn)高端 Gimple

語法樹轉(zhuǎn)高端 Gimple,主要是處理寄存器,比如:“ c = a + b” 并沒有直接的匯編代碼與之對(duì)應(yīng),一般來說需要把 a + b 的結(jié)果保存到寄存器中,然后再把寄存器賦值給 c。另外,調(diào)用一個(gè)新的函數(shù)時(shí)會(huì)進(jìn)入到函數(shù)自己的棧,建棧的操作也需要在 Gimple 中聲明。

4.2 高端 Gimple轉(zhuǎn)低端Gimple

高端 Gimple轉(zhuǎn)低端 Gimple,主要是把變量定義語句執(zhí)行返回語句區(qū)分存儲(chǔ),分配??臻g。
比如:

int a = 1;
a++;
int b = 1;

會(huì)被處理成:

int a = 1;
int b = 1;
a++;

這樣做的好處是很容易計(jì)算一個(gè)函數(shù)到底需要多少??臻g。

此外,return 語句會(huì)被統(tǒng)一處理,放在函數(shù)的末尾,比如:

if (1 > 0) {
return 1;
}
else {
return 0;

}

會(huì)被處理成:

if (1 > 0) {
goto a;
}
else {
goto b;
}
a:
return 1;
b:
return 0;

4.3 低端Gimple轉(zhuǎn)中間代碼

這一步主要是進(jìn)行各種優(yōu)化,添加版本號(hào)等。

5.生成目標(biāo)代碼(匯編代碼)

目標(biāo)代碼也可以叫做匯編代碼。由于中間碼已經(jīng)非常接近于實(shí)際的匯編代碼,它幾乎可以直接被轉(zhuǎn)化。主要的工作量在于針對(duì)不同的CPU生成不同的匯編代碼,兼容各種 CPU 以及填寫模板。在最終生成的匯編代碼中,不僅有匯編命令,也有一些對(duì)文件的說明。

生成目標(biāo)代碼.png

6.匯編(assembly)——匯編器生成二進(jìn)制機(jī)器碼

匯編器會(huì)接收匯編代碼,將它轉(zhuǎn)換成二進(jìn)制機(jī)器碼,生成目標(biāo)文件(后綴是 .o),機(jī)器碼可以直接被 CPU 識(shí)別并執(zhí)行。由于目標(biāo)代碼是分段的,最終的目標(biāo)文件(機(jī)器碼)也是分段的。這是因?yàn)椋?/p>

  1. 數(shù)據(jù)和代碼區(qū)分開。其中代碼只讀,數(shù)據(jù)可寫,方便權(quán)限管理,避免指令被改寫,提高安全性。
  2. 現(xiàn)代 CPU 一般有自己的數(shù)據(jù)緩存和指令緩存,區(qū)分存儲(chǔ)有助于提高緩存命中率。
  3. 當(dāng)多個(gè)進(jìn)程同時(shí)運(yùn)行時(shí),他們的指令可以被共享,這樣能節(jié)省內(nèi)存。

7.鏈接(linking)

鏈接就是將目標(biāo)文件(.o文件)與其中調(diào)用的外部函數(shù)所在的目標(biāo)文件通過重定位關(guān)聯(lián)起來。

在一個(gè)目標(biāo)文件中,不可能所有變量函數(shù)都定義在文件內(nèi)部。比如** strlen 函數(shù)就是一個(gè)被調(diào)用的外部函數(shù),此時(shí)就需要把 main.o 這個(gè)目標(biāo)文件和包含了 strlen 函數(shù)實(shí)現(xiàn)的目標(biāo)文件鏈接起來。我們知道函數(shù)調(diào)用對(duì)應(yīng)到匯編其實(shí)是 jump 指令,后面寫上被調(diào)用函數(shù)的地址,但在生成 main.o 的過程中, strlen() 函數(shù)的地址并不知道,所以只能先用 0 來代替,直到最后鏈接**時(shí),才會(huì)修改成真實(shí)的地址。

鏈接器就是靠著重定位表來知道哪些地方需要被重定位的。每個(gè)可能存在重定位的段都會(huì)有對(duì)應(yīng)的重定位表。在鏈接階段,鏈接器會(huì)根據(jù)重定位表中,需要重定位的內(nèi)容,去別的目標(biāo)文件中找到地址并進(jìn)行重定位。

有時(shí)候我們還會(huì)聽到動(dòng)態(tài)鏈接這個(gè)名詞,它表示重定位發(fā)生在運(yùn)行時(shí)而非編譯后。動(dòng)態(tài)鏈接可以節(jié)省內(nèi)存,但也會(huì)帶來加載的性能問題,這里不詳細(xì)解釋,感興趣的讀者可以閱讀《程序員的自我修養(yǎng)》這本書。(以上是從《大前端開發(fā)者需要了解的基礎(chǔ)編譯原理和語言知識(shí)》直接粘貼過來的)

寫在最后

最后十分感謝《大前端開發(fā)者需要了解的基礎(chǔ)編譯原理和語言知識(shí)》的作者能夠貢獻(xiàn)如此高質(zhì)量的一篇文章,講清楚了整個(gè)編譯過程中的細(xì)節(jié)。本文有大量內(nèi)容是直接從該文章中復(fù)制過來,最終的版權(quán)歸原作者所有,本人只是作為一個(gè)讀后筆記寫下本文,若有侵權(quán)的地方,煩請(qǐng)告知。
參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、以 Hello World開篇 Hello World對(duì)程序員而言肯定是如雷貫耳。但是簡單的事物背后往往包含這...
    ZhengYaWei閱讀 8,668評(píng)論 10 84
  • 前言 2000年,伊利諾伊大學(xué)厄巴納-香檳分校(University of Illinois at Urbana-...
    星光社的戴銘閱讀 16,285評(píng)論 8 180
  • TITLE: 編程語言亂燉 碼農(nóng)最大的煩惱——編程語言太多。不是我不學(xué)習(xí),這世界變化快! 有時(shí)候還是蠻懷念十幾、二...
    碼園老農(nóng)閱讀 5,594評(píng)論 2 35
  • 又是一年 鄉(xiāng)土難離 燈火燭光 鳥兒紛飛 故鄉(xiāng)夢(mèng)里 推開舊窗 緣來淚水 那些年歲 習(xí)慣城市高墻 紅葉隨風(fēng)流浪 沒有山...
    是為一閱讀 268評(píng)論 0 0
  • 姑娘們約酒,沒有心情不好,沒有其他意思,就是想單純聚聚,這有什么理由讓人拒絕? 我們都是以前的同事,那時(shí)候的工作環(huán)...
    蘇穆涼閱讀 288評(píng)論 0 0

友情鏈接更多精彩內(nèi)容