排名好的宜昌網(wǎng)站建設(shè)seo網(wǎng)站排名優(yōu)化教程
程序的編譯和鏈接
- 程序的編譯和鏈接
- 程序的兩種環(huán)境
- 翻譯環(huán)境
- 詳解編譯和鏈接
- 預(yù)處理
- 編譯
- 匯編
- 鏈接
- 運(yùn)行環(huán)境
程序的編譯和鏈接
程序的兩種環(huán)境
在ANSI C的任何一種實(shí)現(xiàn)中,存在兩個(gè)不同的環(huán)境。
第1種是翻譯環(huán)境,在這個(gè)環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機(jī)器指令。
第2種是執(zhí)行環(huán)境,它用于實(shí)際執(zhí)行代碼
翻譯環(huán)境
平時(shí)我們寫的程序都是一個(gè)個(gè)的源文件,那么這些文件是怎么生成
.exe
可執(zhí)行文件的呢?這就是接下來(lái)我們要詳細(xì)研究的內(nèi)容
籠統(tǒng)的來(lái)講,大致過程可以用下圖來(lái)表示:
組成一個(gè)程序的每個(gè)源文件通過編譯過程分別轉(zhuǎn)換成目標(biāo)代碼(object code)。
每個(gè)目標(biāo)文件由鏈接器(linker)捆綁在一起,形成一個(gè)單一而完整的可執(zhí)行程序。
鏈接器同時(shí)也會(huì)引入標(biāo)準(zhǔn)C函數(shù)庫(kù)中任何被該程序所用到的函數(shù),而且它可以搜索程序員個(gè)人的程序庫(kù),將其需要的函數(shù)也鏈接到程序中。
詳解編譯和鏈接
編譯,其實(shí)嚴(yán)格來(lái)說(shuō)應(yīng)該叫做翻譯,因?yàn)?mark>翻譯其實(shí)還分多個(gè)步驟,分別為預(yù)處理,編譯,匯編。在VS中每一步驟是觀察不到的,所以我們使用GCC編譯器來(lái)觀察現(xiàn)象,研究清楚每個(gè)步驟都是做什么的。
預(yù)處理
可以看到我這里有一個(gè)
test.c
文件,里面寫了這樣一段代碼,這時(shí)候我們來(lái)看一下對(duì)它進(jìn)行編譯的第1步預(yù)處理,究竟做了些什么事情,我們用GCC來(lái)執(zhí)行下面的指令:gcc test.c -E > test.i
這句指令意思就是將編譯
test.c
到預(yù)處理那步就停下來(lái),并將所編譯的信息重定向到test.i
中,然后我們打開test.i
看一下,
實(shí)際上已經(jīng)不存在什么定義的宏了,而是直接被替換,另外還值得注意的是原本只有十幾行的代碼,經(jīng)過預(yù)處理之后變成了850行,可以看到預(yù)處理做的事情其實(shí)還是蠻多的,但是重點(diǎn)我們就是來(lái)觀察一下現(xiàn)象,知道預(yù)處理這步到底做了些什么就可以了,所謂預(yù)處理就是對(duì)一些文本進(jìn)行操作,
總結(jié)如下:
1.#include 頭文件的包含,
2.#define 宏替換
3.刪除注釋
都是一些對(duì)文本的操作
編譯
有了第一步的經(jīng)驗(yàn),我們可以來(lái)看一下編譯的步驟,指令如下:
gcc test.i -S > test.s
可以發(fā)現(xiàn),我們的代碼變成了匯編語(yǔ)言,這就是一個(gè)重要的步驟,其實(shí)翻譯成匯編語(yǔ)言之外,還有語(yǔ)義分析,語(yǔ)法分析等等,你的一些語(yǔ)法錯(cuò)誤等等就是這一步檢查出來(lái)的,另外還有一個(gè)很重要的步驟是進(jìn)行符號(hào)匯總,為什么說(shuō)它重要呢,其實(shí)是為下一步生成符號(hào)表做準(zhǔn)備。下一步再說(shuō),總結(jié)編譯步驟:
編譯:
1.語(yǔ)法分析
2.語(yǔ)義分析
3.符號(hào)匯總
4.翻譯成匯編語(yǔ)言
等等
匯編
同樣的操作:
gcc test.s -c > test.o
好的,這下徹底看不懂了,實(shí)際上這是因?yàn)?#xff0c;編譯之后的目標(biāo)文件其實(shí)是二進(jìn)制文件,是無(wú)法識(shí)別的,但是這種類型的文件有它自己的格式叫做
elf
,有個(gè)工具readelf
是可以看這種類型的文件的具體內(nèi)容的,這種文件內(nèi)容實(shí)際上也是有特點(diǎn),都是一段一段的,每一段放不同的信息。我們先說(shuō)匯編這步做了一些什么事情,匯編:
1.翻譯成二進(jìn)制
2.生成符號(hào)表
我們用
readelf
這個(gè)工具要看關(guān)注的也就是這個(gè)符號(hào)表,來(lái)了解一下:
可以看到是有一堆選項(xiàng),我們要用的就是-s,這個(gè)選項(xiàng)來(lái)看這個(gè)符號(hào)表
這個(gè)就是我們匯編這步生成的符號(hào)表,這個(gè)符號(hào)表存儲(chǔ)了文件中符號(hào)的信息,之前我們的示意圖中已經(jīng)說(shuō)了,我們的源文件是有多個(gè)的,每個(gè)文件里面的函數(shù)變量當(dāng)然也是多個(gè)的,那最后我們鏈接起來(lái)怎么找到這些符號(hào)呢?靠的就是這個(gè)符號(hào)表,它會(huì)記錄下每個(gè)符號(hào)的信息,函數(shù),變量,它們的地址等等,在后面的鏈接這步中按照這個(gè)符號(hào)表來(lái)尋找。
鏈接
我相信很多人可能早就聽過,鏈接就是將本地多個(gè)源文件組合起來(lái),并且引入一些外部的庫(kù)等等,但是對(duì)它到底這個(gè)步驟是怎么實(shí)現(xiàn)的并不清楚,
今天就來(lái)研究清楚,先說(shuō)鏈接這個(gè)步驟到底做了什么,
鏈接:
1.合并段表
2.符號(hào)表的合并和重定位
段表這個(gè)今天先暫時(shí)不討論,到后期才能徹底理解,
段表概念:在分段式存儲(chǔ)管理系統(tǒng)中,每個(gè)進(jìn)程或程序都有一個(gè)或多個(gè)邏輯段,為使程序或稱進(jìn)程能正常運(yùn)行,亦即,能從物理內(nèi)存中找出每個(gè)邏輯段所對(duì)應(yīng)的位置,在系統(tǒng)中為每個(gè)進(jìn)程建立一張段映射表,簡(jiǎn)稱段表,段表記錄了進(jìn)程中每一個(gè)段在內(nèi)存中的起始地址(又稱為 “基址” )、段號(hào)和段的長(zhǎng)度。
重點(diǎn)還是上面的符號(hào)表,符號(hào)表合并和重定位是什么意思呢?
舉個(gè)例子:
像是這個(gè)例子就能簡(jiǎn)單的描述一下,符號(hào)表的問題,我們知道每個(gè)文件最后都會(huì)生成一個(gè)目標(biāo)文件,所以每個(gè)源文件的目標(biāo)文件中都有一張符號(hào)表,我們就需要信息合并,當(dāng)
test.c
文件中安裝符號(hào)表去找Add函數(shù)時(shí),發(fā)現(xiàn)找不到(其實(shí)這時(shí)候Add的地址是個(gè)無(wú)效地址),所以就會(huì)報(bào)出了錯(cuò)誤,LNK的錯(cuò)誤一定是鏈接時(shí)發(fā)生的錯(cuò)誤,無(wú)法解析的外部符號(hào)就是根據(jù)符號(hào)表中的信息去找Add這個(gè)符號(hào)找不到。如果
add.c
中將函數(shù)名改成正確的Add,在鏈接時(shí)符號(hào)表合并,就會(huì)將add.c
中Add函數(shù)的地址重定向到合并之后的符號(hào)表中,這樣才能夠順序執(zhí)行。如果當(dāng)我們把
test.c
中的聲明去掉,代碼也是可以很好的跑起來(lái)的,編譯器僅僅是報(bào)個(gè)警告,為什么呢?我想應(yīng)該就很容易理解了,即使你沒有聲明,但是在最后符號(hào)表合并的時(shí)候依舊是很好的進(jìn)行了合并。所以最后的結(jié)果是沒有一點(diǎn)問題的。
運(yùn)行環(huán)境
程序執(zhí)行的過程:
程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個(gè)由操作系統(tǒng)完成。在獨(dú)立的環(huán)境中,程序的載入必須由手工安排,也可能是通過可執(zhí)行代碼置入只讀內(nèi)存來(lái)完成。
程序的執(zhí)行便開始。接著便調(diào)用main函數(shù)。
開始執(zhí)行程序代碼。這個(gè)時(shí)候程序?qū)⑹褂靡粋€(gè)運(yùn)行時(shí)堆棧(stack),存儲(chǔ)函數(shù)的局部變量和返回地址。程序同時(shí)也可以使用靜態(tài)(static)內(nèi)存,存儲(chǔ)于靜態(tài)內(nèi)存中的變量在程序的整個(gè)執(zhí)行過程一直保留他們的值。
終止程序。正常終止main函數(shù);也有可能是意外終止。