編譯器基本原理
1.是什麼(編譯器是什麼) 2.為什麼 (為什麼需要編譯器) 3.怎麼做 (編譯器如何工作)
編譯器是什麼
在編譯器工作之前需要進行預處理,包括巨集的替換,標頭檔案的匯入,以及類似#if的處理 編譯器是一種把源程式語音翻譯成目標程式語言的計算機程式。一般來說,源程式是高階語言比如Java,Objective-C等。 目標程式語言一般就是組合語言或者二進位制碼。
編譯器一般由前端和後端組成。
前端主要進行和源語言相關,和目標語言無關的工作,包括詞法分析,語法分析,語義分析,中間碼生成。 後端主要進行和源語言無關,但是和目標語言有關的工作,比如中間碼優化,將中間碼轉化成目標碼,對目的碼優化生成目標程式。
端 | 編譯器階段 | 生成產物 | 功能 | 用途 |
---|---|---|---|---|
前端 | 詞法分析 | 單詞流 | 語法高亮 | 語法高亮 |
前端 | 語法分析 | AST(抽象語法樹) | 語法高亮,程式碼格式化,語法檢查 | OCLint |
前端 | 中間碼生成 | 中間碼 |
為什麼需要編譯器
自然語言最容易表述人們的要求,當使用者用自然語言表述了需要的功能後,編譯器將高階程式語言轉換成彙編、由彙編到機器碼,提高軟體開發的效率
編譯器如何工作
在命令列輸入clang -ccc-print-phases main.m
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, imag
複製程式碼
1.詞法分析
將原始檔的字串,進行過濾,去除空格,註釋等,然後將其分割成一個個的詞(記號、token)
比如 int a = b + sum(1,2);
會拆分成12個token
int 型別識別符號
a 識別符號
= 賦值運算子
b 識別符號
+ 加號
sum 識別符號
( 左括號
1 整數
, 逗號
2 整數
) 右括號
; 分號
複製程式碼
2.語法分析
詞法分析之後,字元流已經被轉化為token流了
int<int> ID<a> '=' ID<value> '+' ID<sum> '(' Num<1> ',' Num<2> ')' ';'
上面的int表示一個識別符號型別的token。內容為'int'
接下來,解析這個token流,首先這是一個語句,我們主要有用到4種語句,賦值語句,函式呼叫語句,if和while語句。很明顯,這是一個賦值語句。
賦值表示式用變數名、賦值符號= 和表示式構成。
將語法結構應用到token流上,把等號兩邊的內容放到對應的節點上,生成語法樹如下:
接下來對expression<b+sum(1,2)>
進行解析
表示式有很多種,變數表示式,數字表示式,加減法表示式,經過對比,只有加法表示式結構才能匹配,於是將加法表示式的語法結構應用到其中。如圖
進一步解析語法樹的<b>
和<sum(1,2)>
,發現只有變數表示式和函式呼叫表示式才能匹配成功,得到
接下來進行語法優化,有些節點是多餘的,比如在複製表示式中 '='和';',對語法樹進行濃縮得到最終的抽象語法樹(AST)
由此看出,語法分析就是不斷將語法規則用於源程式,將源程式解析成一顆抽象語法樹。之後的語義分析,中間碼生成和程式碼優化都是基於對這棵樹進行遍歷,檢查,修改進行的。
3.語義分析
語義分析階段,會對語法進行多次的遍歷,進行語法檢查,包括型別和宣告的檢查,OClint就是基於語義分析進行靜態程式碼分析的。 還是以上面的賦值語句作為例子, 需要檢查 1.b, sum是否已經宣告過 2.sum函式的引數數量和型別是否和傳入的引數數量和型別匹配。 3.加法運算的兩個運算元的型別是否匹配,這裡也就是檢查函式的返回值型別了 4.賦值運算的兩個運算元型別是否匹配
一般在遍歷語法樹過程中,遇到的變數宣告和函式宣告時,會將變數名-型別,函式名-返回型別-引數數量-引數型別儲存到符號表裡,當遇到使用變數和函式呼叫時,根據名稱在符號表猴子那個查詢,檢查是否宣告過,型別是否匹配。因此,對於java這種函式宣告可以放在使用位置後面的語言,至少需要遍歷兩遍語法樹。
語義檢查時,也會對語法樹進行優化,比如將常量的表示式先計算如:
a = 1 + 2 * 3;
會被優化成
a = 7;
語義分析完成後,編譯期錯誤被排除,所有使用過的變數名和函式名被繫結到宣告的地址,就可以進行程式碼生成和優化了。
中間碼生成
一般的編譯器都不會直接生成目的碼,而是先生成中間碼再生成目的碼。 中間碼的作用: 計算機直接生成的程式碼比人手寫的彙編要龐大、重複很多,電腦科學家們對一些具有固定格式的中間程式碼(最典型的是三地址中間碼)的進行大量的研究工作,提出了很多廣泛應用的、效率非常高的優化演算法,可以對中間程式碼進行優化,比直接對目的碼進行優化的效果要好很多。 通過中間程式碼實現前後級分離,在多系統、多語言開發時,可大幅提高整體開發效率,減少開發成本縮短開發週期。 為了增加編譯器的模組化和可移植化、可擴充套件性。中間程式碼既獨立於任何高階語言,又獨立於機器架構,因此可以通過編寫m+n個編譯模組二獲得m *n種編譯器。