(讀書隨筆)-編譯和鏈接知識梳理

我們在編寫代碼時,往往編譯和運行一氣呵成,但我們很少知道編譯器的處理操作。例如像"hello world"程序在linux下,用GCC來編譯時,只需要最簡單的命令(假設源代碼文件名爲hello.c)

gcc hllo.c
./a.out
hello world

這看似簡單的代碼,過程可以分成四個步驟:預處理、編譯、彙編、鏈接。

預編譯:

1.將所有"#define"刪除,並且展開所有宏定義。

2.處理所有條件預編譯指令,比如“#if”、“#elif”、“#else”、“#endif”。

3.處理“#include”預編譯指令,將被包含的文件插入到該預編譯指令的位置。注意,這個過程是遞歸進行的,也就是說被包含的文件可能包含其他文件。

4.刪除所有的註釋“//”和“/* */”。

5添加行號和文件名標識,比如#“hello.c”2,以便於編譯時編譯器產生調試用的行號信息及用於編譯時產生編譯錯誤或警告時能夠顯示行號。

6.保留所有的#pragma編譯器指令,因爲編譯器須要是用它們。

經過預編譯後的.i文件不包括任何宏定義,因爲所有的宏已經被展開,並且包含的文件已經被插入到.i文件中。所以無法判斷定義是否正確或頭文件包含是否正確時,可以查看預編譯後的文件來確定問題。

編譯:

就是把預處理完的文件進行一系列詞法分析、語法分析、語義分析及優化後生產相應的彙編代碼文件。

彙編:

彙編器將彙編代碼變成機器可以執行的指令,每一個彙編語句幾乎都對應一條機器指令。所以彙編器的彙編過程相對於編譯器來說比較簡單,沒有複雜語法,也沒有語義,也不需要做指令優化,只是根據彙編指令的對照表一一翻譯。使用gcc命令從c源代碼文件開始,經過預編譯、編譯和彙編直接輸出目標文件:

gcc  -c hello.c -o hello.o

鏈接:

我們需要將一大堆文件鏈接起來纔可以得到"a.out",即最終的可執行文件。鏈接的主要內容就是把各個模塊之間相互引用的部分都處理好,使得各個模塊之間能夠正確地銜接。鏈接過程主要包括地址和空間分配、符號決議、重定位等步驟。

最基本的靜態鏈接過程是每個模塊源代碼文件經過編譯器編譯成目標文件(.o或.obj),目標文件和庫一起鏈接形成最終可執行文件。最常見的就是運行時庫,是支持程序運行的基本函數的集合。庫是一組目標文件的包,就是一些常用代碼編譯成目標文件後打包存放。

靜態鏈接的基本過程和作用:使用連接器時,可以直接引用其他模塊的函數和全局變量而無需知道它們的地址,因爲鏈接器在鏈接的時候,會根據所引用的符號,自動對應.c模塊查找其地址,然後將main.c模塊中所有引用到函數的指令重新修正,讓它們的目標地址爲真正的函數地址。

在鏈接過程中,對其他定義在目標文件中的函數調用的指令需要被重新調整,對使用其他定義在其他目標文件的變量來說,也存在相同的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章