下面具體看一下gcc是如何完成以上4個步驟的:編程
//test.c函數式編程
#include int main(void) { printf("Hello World!\n"); return 0; }
這個程序,一步到位的編譯指令是:函數
gcc test.c -o test
運行預處理命令:code
gcc -E test.c -o test.i 或 gcc -E test.c
gcc的-E選項,可讓編譯器在預處理後中止,並輸出預處理結果。開發
gcc的-o選項,用於輸出處理結果到文件中。編譯器
在本例中,預處理結果就是將stdio.h 文件中的內容插入到test.c中
了。io
預處理以後,可直接對生成的test.i文件編譯,生成彙編代碼:編譯
gcc -S test.i -o test.s
gcc的-S選項,表示在程序編譯期間,在生成彙編代碼後,中止,-o輸出彙編代碼文件。test
gcc -c test.s -o test.o
連接階段,這裏涉及一個重要的概念:函數庫gcc
在這個程序中並無定義「printf」的函數實現,且在預編譯中包含進的「stdio.h」中也只有該函數的聲明,而沒有定義函數的實現,那麼,是在哪裏實現「printf」函數的呢?
最後的答案是:系統把這些函數的實現都放到名爲libc.so.6的庫文件中去了,在沒有特別指定時,gcc會到系統默認的搜索路徑「/usr/lib」下進行查找,也就是連接到libc.so.6函數庫中去,這樣就能調用函數「printf」了,而這也正是連接的做用
函數庫有靜態庫和動態庫兩種
靜態庫是指編譯連接時,將庫文件的代碼所有加入到可執行文件中,所以生成的文件比較大,但在運行時也就再也不須要庫文件了,其後綴名一般爲「.a」。 動態庫與之相反,在編譯連接時並無將庫文件的代碼加入到可執行文件中,而是在程序執行時加載,這樣能夠節省系統的開銷。通常動態庫的後綴名爲「.so」,如前面所述的libc.so.6就是動態庫。gcc在編譯時默認使用動態庫。
對於上一小節中生成的test.o,將其與C標準輸入輸出庫進行鏈接,最終生成程序test:
gcc test.o -o test
一般整個程序是由多個源文件組成的,相應地也就造成了多個編譯單元,使用GCC可以很好地管理這些編譯單元。假設有一個由test1.c和 test2.c兩個源文件組成的程序,爲了對它們進行編譯,並最終生成可執行程序test,可使用下面這條命令:
gcc test1.c test2.c -o test
若是同時處理的文件不止一個,GCC仍然會按照預處理、編譯和連接的過程依次進行。若是深究起來,上面這條命令大體至關於依次執行以下三條命令:
gcc -c test1.c -o test1.o gcc -c test2.c -o test2.o gcc test1.o test2.o -o test