c文件是源碼文件。里面的書寫是相對來說,最接近人類語言的C語言。

我們使用gnu工具鏈來使之生成二進制文件。然后,計算機機器就可以執(zhí)行它。

也許你認為很簡單,只需要執(zhí)行g(shù)cc hell.c就可以生成二進制文件了,而如果你使用的是IDE集成開發(fā)環(huán)境的話,那么,也許更簡單,只需要編譯構(gòu)建就可以了。然而,事實并非如此簡單。以上過程,可以分解為4個步驟,分別是:
1. 預(yù)處理prepressing
2. 編譯compilation
3. 匯編assembly
4. 鏈接linking
預(yù)編譯
首先是源代碼hello.c和相關(guān)的頭文件,如stdio.h等被預(yù)編譯器cpp預(yù)編譯成一個.i文件。

預(yù)編譯過程主要處理那些源代碼文件中的以‘#’開始的預(yù)編譯指令。比如“#include”、“#define”等,具體規(guī)則如下:
1. 將所有的“#define”刪除,并展開所有的宏定義。
2. 處理所有條件預(yù)編譯指令,比如“#if、#ifdef、#elif、#else、#endif”等。
3. 處理“#include”預(yù)編譯指令,將被包含的文件插入到該預(yù)編譯指令的位置。注意,這個過程是遞歸進行的,也就是說被包含的文件可能還包含其他文件。
4. 刪除所有的注釋/**/、//。
5. 添加行號和文件名標識。比如# 2 "hello.c" 2。以便于編譯時編譯器產(chǎn)生調(diào)試用的行號信息及用于編譯時產(chǎn)生編譯錯誤或警告時能夠顯示行號。
6. 保留所有的#pragma編譯器指令,因為編譯器需要使用它。
經(jīng)過預(yù)編譯后的.i文件不包含任何宏定義,因為所有的宏已經(jīng)被展開,并且包含的文件也已經(jīng)被插入到.i文件中。所以當(dāng)我們無法判斷宏定義是否正確或頭文件是否包含正確時,可以查看預(yù)編譯后的文件來確定問題。
編譯
編譯過程就是把預(yù)處理完的文件進行一系列詞法分析、語法分析、語義分析及優(yōu)化后生成相應(yīng)的匯編代碼文件,這個過程往往是我們所說的整個程序構(gòu)建的核心部分,也是最復(fù)雜的部分之一。


現(xiàn)在版本的gcc把預(yù)編譯和編譯兩個步驟合成一個步驟,使用一個叫做cc1的程序來完成這兩個步驟。
對于C語言來說,這個預(yù)編譯和編譯程序是cc1,對于c++來說,這個程序是cc1plus,Java是jc1。所以,實際上gcc這個命令指示這些后臺程序的包裝,它會根據(jù)不同的參數(shù)要求去調(diào)用相應(yīng)的預(yù)編譯編譯程序cc1、匯編器as、鏈接器ld。
匯編
匯編器是將代碼轉(zhuǎn)變成機器可以執(zhí)行的指令,每一個匯編語句幾乎都對應(yīng)一條機器指令。所以匯編器的匯編過程相對于編譯器來講比較簡單,它沒有復(fù)雜的語法,也沒有語義,也不需要做指令優(yōu)化,只是根據(jù)匯編指令和機器指令的對照表一一翻譯就可以了,“匯編”這個名字也來源于此。上面的匯編過程我們可以使用匯編器as來完成。

鏈接
鏈接是一個難點,怎么鏈接,鏈接過程到底做了什么,都是揮之不去的疑惑!

可以看到,我們需要將一大堆文件鏈接起來才可以得到“a.out”,即最終的可執(zhí)行文件。其中-lgcc -lgcc_eh –lc這些都是什么參數(shù)?crt1.o、crti.o、crtbeginT.o、crtend.o、crtn.o這些文件是什么?為何要將它們和hello.o連接起來才可以得到可執(zhí)行文件?請看鏈接一文。。。
摘抄自《程序員的自我修養(yǎng)》