下面具體看一下gcc是如何完成以上4個步驟的:
//test.c
#include int main(void) {printf('Hello World!\n');return 0;}
這個程序,一步到位的編譯指令是:
gcc test.c -o test
運行預(yù)處理命令:
gcc -E test.c -o test.i 或 gcc -E test.c
gcc的-E選項,可以讓編譯器在預(yù)處理后停止,并輸出預(yù)處理結(jié)果。
gcc的-o選項,用于輸出處理結(jié)果到文件中。
在本例中,預(yù)處理結(jié)果就是將stdio.h 文件中的內(nèi)容插入到test.c中
了。
預(yù)處理之后,可直接對生成的test.i文件編譯,生成匯編代碼:
gcc -S test.i -o test.s
gcc的-S選項,表示在程序編譯期間,在生成匯編代碼后,停止,-o輸出匯編代碼文件。
gcc -c test.s -o test.o
鏈接階段,這里涉及一個重要的概念:函數(shù)庫
在這個程序中并沒有定義“printf”的函數(shù)實現(xiàn),且在預(yù)編譯中包含進(jìn)的“stdio.h”中也只有該函數(shù)的聲明,而沒有定義函數(shù)的實現(xiàn),那么,是在哪里實現(xiàn)“printf”函數(shù)的呢?
最后的答案是:系統(tǒng)把這些函數(shù)的實現(xiàn)都放到名為libc.so.6的庫文件中去了,在沒有特別指定時,gcc會到系統(tǒng)默認(rèn)的搜索路徑“/usr/lib”下進(jìn)行查找,也就是鏈接到libc.so.6函數(shù)庫中去,這樣就能調(diào)用函數(shù)“printf”了,而這也正是鏈接的作用
函數(shù)庫有靜態(tài)庫和動態(tài)庫兩種
靜態(tài)庫是指編譯鏈接時,將庫文件的代碼全部加入到可執(zhí)行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了,其后綴名通常為“.a”。動態(tài)庫與之相反,在編譯鏈接時并沒有將庫文件的代碼加入到可執(zhí)行文件中,而是在程序執(zhí)行時加載,這樣可以節(jié)省系統(tǒng)的開銷。一般動態(tài)庫的后綴名為“.so”,如前面所述的libc.so.6就是動態(tài)庫。gcc在編譯時默認(rèn)使用動態(tài)庫。
對于上一小節(jié)中生成的test.o,將其與C標(biāo)準(zhǔn)輸入輸出庫進(jìn)行連接,最終生成程序test:
gcc test.o -o test
通常整個程序是由多個源文件組成的,相應(yīng)地也就形成了多個編譯單元,使用GCC能夠很好地管理這些編譯單元。假設(shè)有一個由test1.c和 test2.c兩個源文件組成的程序,為了對它們進(jìn)行編譯,并最終生成可執(zhí)行程序test,可以使用下面這條命令:
gcc test1.c test2.c -o test
如果同時處理的文件不止一個,GCC仍然會按照預(yù)處理、編譯和鏈接的過程依次進(jìn)行。如果深究起來,上面這條命令大致相當(dāng)于依次執(zhí)行如下三條命令:
gcc -c test1.c -o test1.ogcc -c test2.c -o test2.ogcc test1.o test2.o -o test