編譯器相關

Yang1492955186752發表於2017-12-13

編譯器基本原理

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語句。很明顯,這是一個賦值語句。

語法結構樹.pic.jpg

賦值表示式用變數名、賦值符號= 和表示式構成。

將語法結構應用到token流上,把等號兩邊的內容放到對應的節點上,生成語法樹如下:

14972724543701.jpg

接下來對expression<b+sum(1,2)>進行解析 表示式有很多種,變數表示式,數字表示式,加減法表示式,經過對比,只有加法表示式結構才能匹配,於是將加法表示式的語法結構應用到其中。如圖

14972725257900.jpg

進一步解析語法樹的<b><sum(1,2)>,發現只有變數表示式和函式呼叫表示式才能匹配成功,得到

14972730581069.jpg

接下來進行語法優化,有些節點是多餘的,比如在複製表示式中 '='和';',對語法樹進行濃縮得到最終的抽象語法樹(AST)

14972734592756.jpg

由此看出,語法分析就是不斷將語法規則用於源程式,將源程式解析成一顆抽象語法樹。之後的語義分析,中間碼生成和程式碼優化都是基於對這棵樹進行遍歷,檢查,修改進行的。

3.語義分析

語義分析階段,會對語法進行多次的遍歷,進行語法檢查,包括型別和宣告的檢查,OClint就是基於語義分析進行靜態程式碼分析的。 還是以上面的賦值語句作為例子, 需要檢查 1.b, sum是否已經宣告過 2.sum函式的引數數量和型別是否和傳入的引數數量和型別匹配。 3.加法運算的兩個運算元的型別是否匹配,這裡也就是檢查函式的返回值型別了 4.賦值運算的兩個運算元型別是否匹配

一般在遍歷語法樹過程中,遇到的變數宣告和函式宣告時,會將變數名-型別,函式名-返回型別-引數數量-引數型別儲存到符號表裡,當遇到使用變數和函式呼叫時,根據名稱在符號表猴子那個查詢,檢查是否宣告過,型別是否匹配。因此,對於java這種函式宣告可以放在使用位置後面的語言,至少需要遍歷兩遍語法樹。

語義檢查時,也會對語法樹進行優化,比如將常量的表示式先計算如: a = 1 + 2 * 3; 會被優化成 a = 7;

語義分析完成後,編譯期錯誤被排除,所有使用過的變數名和函式名被繫結到宣告的地址,就可以進行程式碼生成和優化了。

中間碼生成

一般的編譯器都不會直接生成目的碼,而是先生成中間碼再生成目的碼。 中間碼的作用: 計算機直接生成的程式碼比人手寫的彙編要龐大、重複很多,電腦科學家們對一些具有固定格式的中間程式碼(最典型的是三地址中間碼)的進行大量的研究工作,提出了很多廣泛應用的、效率非常高的優化演算法,可以對中間程式碼進行優化,比直接對目的碼進行優化的效果要好很多。 通過中間程式碼實現前後級分離,在多系統、多語言開發時,可大幅提高整體開發效率,減少開發成本縮短開發週期。 為了增加編譯器的模組化和可移植化、可擴充套件性。中間程式碼既獨立於任何高階語言,又獨立於機器架構,因此可以通過編寫m+n個編譯模組二獲得m *n種編譯器。

相關文章