以前學(xué)的程序的執(zhí)行過程是編輯掉瞳、編譯饮醇、鏈接它抱、執(zhí)行。今天這本書把這個過程更加細(xì)化了朴艰,它以C語言中的helloworld程序為例進(jìn)行說明观蓄,講的大概是從編譯到鏈接的過程混移。
也是包括4步:1、預(yù)處理侮穿;2歌径、編譯;3亲茅、匯編回铛;4、鏈接克锣。從這個順序可以看出在C語言中預(yù)處理是在編譯之前茵肃。
預(yù)編譯是個獨立的過程,不同于源文件的.cpp格式和頭文件的.h格式袭祟,預(yù)編譯得到的文件后綴是.i或者.ii验残。
預(yù)編譯的主要動作就是處理代碼中以#開頭的指令,具體可見P64這些步驟巾乳。因為宏已經(jīng)展開所以.i文件不包含任何宏定義您没。可以根據(jù).i文件查看宏定義和文件包含是否正確胆绊。
預(yù)編譯需要預(yù)編譯器氨鹏。
編譯的過程是把預(yù)處理得到的文件進(jìn)行詞法分析、語法分析压状、語義分析和優(yōu)化后生成相應(yīng)的匯編代碼文件喻犁。
匯編階段是通過匯編器完成的,其作用就是把匯編指令轉(zhuǎn)換成機器指令何缓。匯編結(jié)束以后生成目標(biāo)文件.obj肢础。
鏈接簡而言之就是把目標(biāo)文件鏈接在一起生成可執(zhí)行文件的過程,但是實際上這是一個非常復(fù)雜的過程碌廓,并不像看上去那么簡單传轰。
編譯的過程可以分為掃描、語法分析谷婆、語義分析慨蛙、源代碼優(yōu)化、代碼生成纪挎、目標(biāo)代碼優(yōu)化等6步期贫。
這一過程是交給掃描器執(zhí)行的,目的是把程序語句劃分成若干記號异袄。
這些記號一般包括:1通砍、關(guān)鍵字;2、標(biāo)識符封孙;3迹冤、字面量(數(shù)字,字符串等)虎忌;4泡徙、特殊符號(加號,等號等)膜蠢。
此外堪藐,掃描器還將標(biāo)識符放到符號表,將字面量放到文字表中以備后用挑围。
詞法分析需要此法掃描器礁竞。
它是對詞法分析產(chǎn)生的各種記號進(jìn)行語法分析,并產(chǎn)生一顆語法樹贪惹。
語句內(nèi)容含義的區(qū)分苏章,語法的檢查等都是在此階段完成的寂嘉。
語法分析需要語法分析器奏瞬。
語義分析需要語義分析器。
語義分析就是分析該語句的意思泉孩,就是它能做什么硼端,有啥用。
編譯器所能做的包括靜態(tài)語義分析和動態(tài)語義分析寓搬。
靜態(tài)語義:編譯期能夠確定的語義珍昨,它主要包括類型和聲明的匹配,類型的轉(zhuǎn)換等句喷。我想C++中的靜態(tài)綁定應(yīng)該也屬于靜態(tài)語義吧镣典。
動態(tài)語義:運行期能夠確定的語義以及相關(guān)問題,比如說異常處理唾琼。我同時在想C++中的動態(tài)綁定應(yīng)該屬于動態(tài)語義兄春。
語義分析對語法樹各節(jié)點進(jìn)行了類型標(biāo)記和類型轉(zhuǎn)換,還更新了符號表里的符號類型锡溯。
編譯器有很多層次的優(yōu)化赶舆,源碼級別的優(yōu)化是其中一個層次。
源碼級的優(yōu)化需要源碼級優(yōu)化器祭饭。
這個優(yōu)化是把語法樹轉(zhuǎn)換成中間代碼芜茵,并在中間代碼上進(jìn)行的。
常見的中間代碼有三地址碼和P代碼倡蝙。
中間代碼將編譯器分成了前端和后端九串,前端負(fù)責(zé)產(chǎn)生與機器無關(guān)的中間代碼,后端負(fù)責(zé)把中間代碼轉(zhuǎn)換成目標(biāo)代碼寺鸥。
跨平臺的編譯器并不是放在任意一個平臺上都絕對能用蒸辆,只不過它能支持的平臺很多而已征炼。這是因為編譯器使用同一個前端,而針對不同的平臺使用不同的后端躬贡。
編譯器的后端包括代碼生成器和目標(biāo)代碼優(yōu)化器谆奥。
代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)代碼,該過程依賴于目標(biāo)機器拂玻。
目標(biāo)代碼優(yōu)化器對目標(biāo)代碼進(jìn)行優(yōu)化酸些,比如選擇合適的尋址方式,以移位代替數(shù)乘等檐蚜。
現(xiàn)在的編譯器非常復(fù)雜魄懂,上述提到的這些方面也變得非常復(fù)雜。
變量和函數(shù)的地址都是在最終鏈接的時候才確定的闯第,然后變成可執(zhí)行文件市栗。
作者把鏈接比喻為拼圖的拼接。
將源代碼模塊組裝起來的過程就是鏈接咳短。
鏈接的過程包括:1填帽、地址和空間分配;2咙好、符號決議篡腌;3、重定位等勾效。
.obj文件即目標(biāo)文件和庫一起鏈接成可執(zhí)行文件嘹悼。
庫是由一些常用的代碼編譯成的目標(biāo)文件的包,是一個集合层宫。最常見的庫是運行時庫级零,是支持程序運行的基本函數(shù)的集合彪见。
每個目標(biāo)文件都是單獨編譯的剔桨。
模塊A想要調(diào)用模塊B的C函數(shù)岳掐,A必須要知道C的地址,但是現(xiàn)在A不知道C的地址哮奇,但是A給C留了位置膛腐,等到鏈接器鏈接時再在這個位置上填上C的地址。如果C的地址被改動了鼎俘,A中所有調(diào)用C的地方都需要進(jìn)行相應(yīng)的更改哲身,這些都可藉由鏈接器完成。這是靜態(tài)鏈接的基本功能和作用贸伐。
在鏈接的過程中需要對目標(biāo)文件中定義在其他目標(biāo)文件中的函數(shù)和變量的調(diào)用指令進(jìn)行重新調(diào)整勘天,注意這里說的是指令!書中舉的例子意在說明,當(dāng)目標(biāo)文件A調(diào)用目標(biāo)文件B中的變量C時脯丝,因為暫時無法知道C的位置商膊,所以指令先把表示C的位置置為某一值,等到鏈接的時候再把這值修正為C的地址宠进,這一過程叫做重定位晕拆,像C這樣的位置被稱為重定位入口。