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