c语言程序链接过程,C语言简明教程(二):C程序编译链接过程和实例对照详解...
不像高級編程語言,在C語言開發(fā)中,了解其編譯鏈接過程顯得相對重要,因為C語言是較為底層的語言,很多時候我們調(diào)試C程序或者解決其它問題都可能會涉及到C編譯鏈接的相關(guān)知識,例如編譯動態(tài)庫或者靜態(tài)庫。下面我們一起來了解一下C程序的編譯鏈接過程,結(jié)合一些實例更好了解其中的原理,這里使用的編輯器是linux的vim,編譯器使用GCC。
C程序編譯的起始點為源代碼(hello.c),結(jié)果為可執(zhí)行的字節(jié)碼文件,C程序的整體編譯鏈接過程如下圖所示:
在整個過程中,最重要的兩個環(huán)節(jié)為編譯階段和鏈接階段,編譯由編譯器完成,鏈接由鏈接器完成,編譯器的最主要功能是將C源代碼編譯成中間代碼即目標代碼,鏈接器的功能是將目標代碼和庫文件代碼鏈接成可執(zhí)行代碼,即可執(zhí)行程序。鏈接器涉及的結(jié)構(gòu)不會太復(fù)雜,一般會由編譯器自動調(diào)用,我們使用編譯器編譯源代碼的時候,編譯器會自動調(diào)用鏈接器完成代碼生成。C程序詳細編譯鏈接過程如下:
下面我們就預(yù)處理、編譯、匯編、鏈接進行詳細解釋,并且結(jié)合操作實例進行理解。
一、預(yù)處理(Preprocess)
預(yù)處理由預(yù)處理器(Preprocessor)負責執(zhí)行,這個過程并不對源代碼進行解析,預(yù)處理器負責掃描源代碼,處理包含頭文件,宏處理命令,條件編譯命令。總的來說有4個編譯階段:
1、替換三字符組合雙字符組
2、行處理,將源碼中的行轉(zhuǎn)義字符轉(zhuǎn)為一般的換行符
3、處理源碼中的注釋、空白等
4、處理源碼中的預(yù)處理命令和宏擴展
下面看一個簡單的C程序源碼,這個代碼定義了一個字符串,打印該字符串和該字符串的長度:
#include
#include
#include
// the first C Program
int main(int argc, char *argv[])
{
char *name = "hello"; // defined name of the user
printf("%s %ld\n", name, strlen(name)); // print the name and the length of the name
return 0;
}
為了驗證這個預(yù)編譯過程,我們目前只需要關(guān)注注釋就行了,使用gcc預(yù)處理命令:gcc -E hello.c -o hello.i,-E表示執(zhí)行預(yù)處理,-o表示指定輸出文件,為什么是i后綴名?在這里你也可以取其它名字的后綴名,使用vim查看hello.i文件如下圖所示:
你會發(fā)現(xiàn)所有的注釋都沒有了,即驗證了預(yù)處理過程,但是還不夠,在這個過程中涉及到我們編寫代碼的內(nèi)容,最明顯的就是#include命令了,這是一個預(yù)處理命令,但是這是什么意思呢?首先我們要清楚C程序一般來說實現(xiàn)某一個功能的時候,會先在頭文件(.h)中聲明,后再在源文件(.c)中實現(xiàn)。那么include包含文件的具體含義是什么?就是將需要的頭文件(如string.h)包含進hello.c文件里,相當于將整個string.h頭文件的內(nèi)容復(fù)制一遍放到hello.c文件里,真的嗎?不會出錯?在C語言任何東西都需要先聲明或定義后使用,如果你先聲明一個函數(shù)但是沒有定義實現(xiàn),調(diào)用該函數(shù)執(zhí)行預(yù)處理這里也不會出錯。
上面的代碼中我們使用了了一個strlen()函數(shù),這個函數(shù)在string.h頭文件中,那么按照我們說的,hello.i中就會包含該函數(shù)的聲明(注意聲明和定義不是一個東西),使用vim打開,查找一下看:
真的是這樣!另外還可以看到strtok函數(shù),該函數(shù)用于分割字符串,也是處理字符串常用的一個函數(shù)。
預(yù)處理過程比較接近我們的代碼編程,比如有時可能會處理重復(fù)包含文件、條件編譯等問題,例如下面的條件編譯,即使run函數(shù)沒有定義,也能通過編譯該源碼并執(zhí)行,為什么呢?因為它根本就沒有參與到實際的代碼編譯。
二、編譯(Compile)
編譯使用編譯器處理,主要是將預(yù)處理好的代碼編譯成匯編代碼,這個過程比較復(fù)雜,一般來說匯編代碼就是目標代碼了,因為目標代碼是機器代碼,而匯編代碼只是機器代碼的一種助記符,這種助記符不像高級語言和機器語言的區(qū)別一樣,高級語言是有語義的,但是匯編助記符基本沒什么語義,一個匯編指令就能對應(yīng)一個機器碼。使用gcc –S hello.i –o hello.s編譯,使用vim查看hello.s文件如下圖所示:
上面的main就是main函數(shù),從main開始執(zhí)行,另外匯編代碼是分段的,上面是數(shù)據(jù)段,下面是程序代碼段,最終的可執(zhí)行代碼也分段,它對應(yīng)于內(nèi)存分區(qū),在C語言和其它編程語言里都涉及到內(nèi)存分區(qū)的分析,你可能聽說過棧區(qū)和堆區(qū)等,這就是可執(zhí)行代碼和內(nèi)存的聯(lián)系了。
三、匯編(Assembly)
匯編過程就是使用匯編器將匯編代碼翻譯成目標機器代碼,在這一步執(zhí)行的結(jié)果就是真的機器碼了,但是要注意,這個機器碼還不能執(zhí)行的,為什么?前面提到過,在預(yù)編譯階段僅僅是將頭文件包含進來,但是并沒有對應(yīng)實現(xiàn)的文件代碼,所以這個字節(jié)碼文件還不是最終的產(chǎn)品。
執(zhí)行匯編命令使用gcc
–c hello.s –o hello.o,使用objdump –s –d hello.o查看該字節(jié)碼文件的結(jié)構(gòu):
這是elf文件(windows下為PE文件),.text為代碼段,.rodata為數(shù)據(jù)段。
四、鏈接(Link)
鏈接階段使用鏈接器執(zhí)行,上面說了,預(yù)編譯中有些系統(tǒng)頭文件的實現(xiàn)沒有包含進來,那么在這一步就需要將需要的文件包含進來了,直接使用gcc hello.o –o hello即可得到最終的可執(zhí)行文件。
在這一步又有一個很重要的學(xué)問了,靜態(tài)庫和動態(tài)庫。如果使用靜態(tài)庫,那么在這一步鏈接器就會將靜態(tài)庫和本項目的字節(jié)碼文件一起打包生成最終的可執(zhí)行文件,如果使用動態(tài)庫,那么在這一步也有處理,但是不會將動態(tài)庫一起打包,而是在運行的時候才動態(tài)加載。這兩種庫開發(fā)中都會用到,一般使用其它庫都需要首先引入頭文件,為什么?和第一步預(yù)編譯有關(guān),首先有頭文件聲明才能進行參與編譯過程,而庫文件一般都是代表實現(xiàn)頭文件的代碼生成的字節(jié)碼文件。
了解更多關(guān)于編譯和鏈接的內(nèi)容,可以查看編譯原理簡明教程:
總結(jié)
以上是生活随笔為你收集整理的c语言程序链接过程,C语言简明教程(二):C程序编译链接过程和实例对照详解...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux band0 手动重启,lin
- 下一篇: 暴力除法C语言,暴力除法