隨想錄(編譯器是怎麼工作的)

2021-08-26 21:27:58 字數 3457 閱讀 5699

其實,現在的編譯器早已經突破了原來的概念。比如說,編譯器最終的**不一定在實際機器上執行,可能是虛擬機器;編譯器編譯語言時不一定需要生成可執行檔案,能解釋就行;編譯器最好並行編譯;編譯器不一定很大,可能十幾個檔案就可以,比如說lua等等。不過,我們今天說的編譯器還是比較傳統的c編譯器,有興趣的同學可以看看編譯器是怎麼幫助我們生成可執行檔案的。我們按照詞法、語法、語義、優化的順序逐一展開。現在假設有這樣一段**,

#include #define max_value 7

int test(int value)

int main(int argc, char* argv)

(1)詞法分析

詞法分析是整個檔案編譯最基本的環節。上面的檔案中就存在很多的字元,那我們就需要分別對它們進行處理。比如說,通常的分類很可能是這樣的,

a)字元是否是數字,例如7,1,4

b)字元是否是string型別,例如「p = %d」

c)字元是否是關鍵字,例如define

d)字元是否是變數,例如value,argc, argv, p

e)字元是否是運算子, 例如 +

f)字元是否是圓括號、方括號、花括號等等

(2)語法分析

語法分析的目的就是構建乙個語法樹,分析當前的檔案是否符合程式語言的文法結構,比如說,

a)整個字串是否符合表示式要求

b)字串是否符合判斷語句要求

c)字串是否符合迴圈語句要求

d)字串是否符合函式要求

e)字串是否符合include語法要求

f) 有沒有沒有未宣告就是用的變數等等;

(3)語義分析

語義分析有的時候和語法分析是聯絡在一起的。但是,這裡我們把它拆開來單獨成了一部分。所謂的語義分析,其實就是把前面生成的語法樹拆解下來,生成原子語句操作的過程。比如說,上面的檔案很可能是這樣的形式,

set value

mov temp1[inner], 7

add temp1[inner], 1

mul temp2,value[param], 4

add temp1, temp2

mov result, temp1

popset argc[param]

set argv[param]

set 3

call test

pop

get result

mov p[inner], result

set p

set string "p = %d"

poppop

mov result, 1

poppop

這裡需要解釋一下,語義轉換的結構和形式其實是各個編譯器自己定義的,未必有通用的結構。這裡的語句只是我自己想出來的,可能和實際的形式有很大的出入,但是基本方法應該是一樣的。主要解釋如下,

a)set值為函式引數

b)call為函式呼叫

c)pop為堆疊平衡使用

d)資料[inner],表示當前變數是函式中的臨時變數

e)資料[param],表示當前變數是入參引數

f)temp表示編譯器為了自身計算方便,臨時新增的區域性變數

g)result表示返回值

(4)**優化

**優化是編譯器處理的乙個重要環節,**優化的目的主要是減少不必要的計算和處理,比如

a)計算沒有使用價值的臨時變數

b)除去沒有判斷價值的if語句

c)對於某些const變數,編譯器提前計算,這裡就可以對temp1提前計算

d)其他優化措施等等

(5)生成彙編**

在(3)中生成的**只是中間**,並不是完全意義上的組合語言。所以,編譯器還需要把它翻譯成對應的二進位制**,比如說arm語言、x86語言或者是powerpc語言等等。當然這中間還是存在一些技巧的,比如

a)對於多引數的函式,某些cpu可以用暫存器代替,有些cpu用堆疊表示

b)某些cpu需要對位元組對齊,某些cpu則不需要

c)某些cpu有位元組序的要求,某些cpu則無所謂,而有的cpu則可選

d)對於臨時變數,有的cpu可以暫存器表示,而有的cpu只能自己生成乙個temp變數等等,

說到這裡,我們也可以自己小試一下身手,看看**怎麼生成,熟悉x86**的同學也可以自己試試,

push ebp

mov ebp, esp

push ebx

push ecx

mov ebx, 8

mov ecx, ebp[8]

mul ecx, 4

add ebx, ecx

mov eax, ebx

pop ecx

pop ebx

mov esp, ebp

pop ebp

push ebp

mov ebp, esp

sub esp, 0x4

push 3

call test

add esp 4

mov ebp[-4], eax

push ebp[-4]

push string "p = %d"

call printf

add esp, 8

mov eax, 1

sub esp, 0x4

mov esp, ebp

pop ebp

(6)彙編級**優化

這裡的優化其實還挺多的,但是功能基本有限,無外乎就是,

a)乘法轉變成移位

b)除法轉變成移位

c)暫存器優化使用

d)刪除暫存器的重複操作過程

e)部分函式引數用暫存器代替等等

(7)鏈結和生成可執行檔案

在編譯過程中,我們常常看到有些**編譯通過了,但是鏈結失敗了。這是很正常的事情,因為在最後生成的檔案當中,每乙個變數和函式都應該有出處,否則就會鏈結失敗。不管是什麼系統平台,鏈結都是個大學問。這個時候,做的事情其實還是比較多的,比如

a)生成執行檔案,確定是否帶除錯資訊

b)鏈結所有的變數和**

c)生成map檔案

d)確定函式和變數的出處,一旦查詢失敗,結束

e)調整變數和函式**的位置,填寫檔案結構,生成最終可執行檔案

隨想錄(編譯器是怎麼工作的)

其實,現在的編譯器早已經突破了原來的概念。比如說,編譯器最終的 不一定在實際機器上執行,可能是虛擬機器 編譯器編譯語言時不一定需要生成可執行檔案,能解釋就行 編譯器最好並行編譯 編譯器不一定很大,可能十幾個檔案就可以,比如說lua等等。不過,我們今天說的編譯器還是比較傳統的c編譯器,有興趣的同學可以...

隨想錄(lcc編譯器)

lcc編譯器是一款開源編譯器,和我們之前談過的ucc差不多。一開始的時候,這款編譯器是用來進行教學使用的,但是後來越來越多的人開始了解它 使用它,並且將這款編譯器用到實際專案當中。當前一般的用法就是利用lcc將c檔案轉變成asm彙編檔案,這種使用方法是最常見的。如果我們自己開發的程式是微控制器軟體 ...

隨想錄(c編譯器的實現)

無意中在github上發現乙個很有意思的專案,比如這裡它利用flex實現了字串的識別,利用bison實現了ast語法樹的構建,最後直接利用ast進行計算和識別。ast節點遍歷的時候,作者應該是根左右遍歷的,其實左右根遍歷或許更好一些。注意,語法解析的時候肯定是樹的結構,但是不一定是二叉樹。比如,它的...