(摘自《程序員的自我修養(yǎng)》)
1. 被隱藏了的過程
??當(dāng)我們使用gcc來編譯一個(gè)程序時(shí)困后,例如
gcc hello.c
??運(yùn)行結(jié)束后會(huì)生成一個(gè)可執(zhí)行文件如暖,事實(shí)上,該命令執(zhí)行成功的背后可分為四個(gè)步驟:預(yù)處理(Prepressing)、編譯(Compilation)座咆、匯編(Assembly)、鏈接(Linking)
1.1 預(yù)編譯
??預(yù)編譯主要處理那些源代碼文件中以“#”開始的預(yù)編譯指令绵载。比如“#include”饲梭、“#define”等,主要處理規(guī)則如下:
- 將所有的“#define”刪除早歇,并且展開所有宏定義倾芝。
- 處理所有條件預(yù)編譯指令,例如“#if”箭跳、“#else”晨另、“#endif”等。
- 處理“#include”預(yù)編譯指令谱姓,將被包含的文件插入到該預(yù)編譯指令的位置借尿。這個(gè)過程是遞歸進(jìn)行的。
- 刪除所有的注釋代碼屉来。
- 添加行號(hào)和文件名標(biāo)識(shí)路翻,以便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息及用于編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告時(shí)能顯示行號(hào)。
- 保留所有的“#pragma”編譯器指令茄靠,因?yàn)榫幾g時(shí)編譯器須使用它們茂契。
1.2 編譯
??編譯就是把預(yù)處理后的文件進(jìn)行一系列詞法分析、語法分析慨绳、語義分析和優(yōu)化后生成相應(yīng)的匯編代碼文件掉冶。
1.3 匯編
??匯編器是將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令,每一條語句基本都對應(yīng)一條機(jī)器指令脐雪。
1.4 鏈接
??先關(guān)注一下上面的內(nèi)容郭蕉,再來分析鏈接過程。
2. 編譯器做了什么
??編譯過程一般可以分為6步:掃描喂江、語法分析召锈、語義分析、源代碼優(yōu)化获询、代碼生成和目標(biāo)代碼優(yōu)化涨岁。
2.1 詞法分析
??首先源代碼程序被輸入到掃描器,掃描器的任務(wù)很簡單吉嚣,它只是簡單地進(jìn)行詞法分析梢薪,運(yùn)用一種類似于有限狀態(tài)機(jī)的算法可以很輕松地將源代碼的字符序列分割成一系列的記號(hào)。這些記號(hào)一般分為如下幾類:關(guān)鍵字尝哆、標(biāo)識(shí)符秉撇、字面量(包含數(shù)字和字符串等)和特殊符號(hào)(如加號(hào)、等號(hào))。在識(shí)別記號(hào)的同時(shí)琐馆,掃描器也完成了其他工作规阀。比如將標(biāo)識(shí)符存放到符號(hào)表,將數(shù)字瘦麸、字符串常量存放到文字表等谁撼,已被后面的步驟使用。
2.2 語法分析
??接下來語法分析器將對掃描器產(chǎn)生的記號(hào)進(jìn)行語法分析滋饲,從而產(chǎn)生語法樹厉碟。整個(gè)分析過程采用了上下文無關(guān)語法的分析手段。簡單地講屠缭,由語法分析器產(chǎn)生的語法樹就是以表達(dá)式為節(jié)點(diǎn)的樹箍鼓。在這個(gè)階段,如果出現(xiàn)了表達(dá)式不合法呵曹,比如括號(hào)不匹配款咖,表達(dá)式中缺少操作符等,編譯器就會(huì)報(bào)告語法分析階段的錯(cuò)誤逢并。
2.3 語義分析
??語法分析器僅僅完成了對表達(dá)式的語法層面的分析之剧,但它并不了解這個(gè)語句是否真正有意義。比如C語言里面兩個(gè)指針做乘法運(yùn)算是沒有意義的砍聊,但是這個(gè)語句在語法上是合法的背稼,編譯器能夠分析的語義是靜態(tài)語義,所謂的靜態(tài)語義是指在編譯期可以確定的語義玻蝌,與之對應(yīng)的動(dòng)態(tài)語義就是只有在運(yùn)算期才能確定的語義蟹肘。
??靜態(tài)語義通常包括聲明和類型的匹配,類型的轉(zhuǎn)換俯树,比如當(dāng)一個(gè)浮點(diǎn)型的表達(dá)式轉(zhuǎn)換為一個(gè)整型的表達(dá)式時(shí)帘腹,其中隱含了一個(gè)從浮點(diǎn)型到整型的轉(zhuǎn)換過程许饿,語義分析中需要完成這個(gè)步驟阳欲。比如將一個(gè)浮點(diǎn)型賦值個(gè)一個(gè)指針時(shí),語義分析程序會(huì)發(fā)現(xiàn)這個(gè)類型不匹配陋率,編譯器將會(huì)報(bào)錯(cuò)球化。動(dòng)態(tài)語義一般指在運(yùn)行期出現(xiàn)的語義相關(guān)的問題,比如將0作為除數(shù)是一個(gè)運(yùn)行期語義錯(cuò)誤瓦糟。
??經(jīng)過語義分析之后筒愚,整個(gè)語法樹表達(dá)式都被標(biāo)識(shí)了類型,如果有些類型需要做隱式轉(zhuǎn)換菩浙,語義分析程序會(huì)在語法樹中插入相應(yīng)的轉(zhuǎn)換節(jié)點(diǎn)巢掺。
2.4 中間語言生成
??中間代碼是由源代碼優(yōu)化器通過轉(zhuǎn)化語法樹生成的句伶,它是語法樹的順序表示,已經(jīng)非常接近目標(biāo)代碼了陆淀。但是它一般跟目標(biāo)機(jī)器和運(yùn)行時(shí)環(huán)境是無關(guān)的考余,比如它不包含數(shù)據(jù)的尺寸、變量地址和寄存器的名字等倔约。中間代碼有多種類型秃殉,比較常見的有P-代碼和三地址碼,最基本的三地址碼是這樣的:
x = y op z
??這個(gè)三地址碼表示將y和z進(jìn)行op操作后賦值給x坝初。
??假設(shè)存在代碼如下:
array[index] = (index + 4) * (2 + 6)
??這段代碼生成的語法樹轉(zhuǎn)換成中間代碼為:
t1 = 2 + 6
t2 = index + 4
t3 = t2 * t1
array[index] = t3
??為了使所有的操作都符合三地址碼形式浸剩,這里采用了幾個(gè)臨時(shí)變量:t1、t2和t3鳄袍,在三地址碼基礎(chǔ)上進(jìn)行優(yōu)化時(shí)绢要,優(yōu)化程序會(huì)將(2+6)的結(jié)果計(jì)算出來,得到t1=8拗小,然后將后面的t1替換為8重罪,還可以省去一個(gè)臨時(shí)變量t3,因?yàn)閠2可以重復(fù)使用哀九,經(jīng)過優(yōu)化以后的代碼如下:
t2 = index + 4
t2 = t2 * 8
array[index] = t2
??中間代碼使得編譯器可以被分為前端和后端剿配。編譯器前端負(fù)責(zé)產(chǎn)生機(jī)器無關(guān)的中間代碼,編譯器后端將中間代碼轉(zhuǎn)換成目標(biāo)機(jī)器代碼阅束。這樣對于一些可以跨平臺(tái)的編譯器而言呼胚,它們可以針對不同的平臺(tái)使用同一個(gè)前端和針對不同機(jī)器平臺(tái)的數(shù)個(gè)后端。
2.5 目標(biāo)代碼生成和優(yōu)化
??源代碼級(jí)優(yōu)化器產(chǎn)生中間代碼標(biāo)志著下面的過程都屬于編輯器后端息裸。編譯器后端主要包括代碼生成器和目標(biāo)代碼優(yōu)化器蝇更。代碼生成器將中間代碼生成目標(biāo)機(jī)器代碼,這個(gè)過程十分依賴于目標(biāo)機(jī)器呼盆,因?yàn)椴煌瑱C(jī)器有著不同的字長年扩、寄存器、整數(shù)數(shù)據(jù)類型和浮點(diǎn)數(shù)數(shù)據(jù)類型等访圃。對于上面例子的中間代碼厨幻,代碼生成器可能會(huì)生成下面的代碼序列(用x86匯編語言表示):
movl index, %ecx ;將index的值寫入ecx寄存器
addl $4, %ecx ;ecx = ecx + 4
mull $8, %ecx ;ecx = ecx * 8
movl index, %eax ;將index的值寫入eax寄存器
movl %ecx, array(,eax,4) ;array[index] = ecx
??最后目標(biāo)代碼優(yōu)化器對上述的目標(biāo)代碼進(jìn)行優(yōu)化,
腿时。况脆。。未完待續(xù)