c++程序編譯過程及相關概念

編譯

把源文件中的源代碼翻譯成機器語言,保存到目標文件中。如果編譯通過,就會把CPP轉換成OBJ文件。

編譯單元:

每個cpp就是一個編譯單元,每個編譯單元相互之間是獨立且相互不知的一個編譯單元(Translation Unit)是指一個.cpp文件以及這所include的所有.h文件,.h文件裏面的代碼將會被擴展到包含它的.cpp文件裏,然後編譯器編譯該.cpp文件爲一個.obj文件,後者擁有PE(Portable Executable,即Windows可執行文件)文件格式,並且本身包含的就是二進制代碼,但是不一定能執行,因爲並不能保證其中一定有main函數。當編譯器將一個工程裏的所有.cpp文件以分離的方式編譯完畢後,再由鏈接器進行鏈接成爲一個.exe或.dll文件

編譯過程

目標文件:

編譯後生成的文件,以機器碼的形式包含了編譯單元裏所有的函數和數據、導出符號表、未解決符號表、地址重定向表
導出符號表:即該目標文件可以提供的符號及地址。
未解決符號表:即找不到地址的符號的列表,告訴鏈接器這些符號沒找到地址。
地址重定向表:鏈接的時候,鏈接器會爲目標文件的「未解決符號表」裏的符號在其他目標文件中尋找地址,但是每個目標文件的地址都是從0x0000開始的,這樣直接將對方文件中符號的地址拿過來用顯然會是不正確的,爲了區分不同的文件,鏈接器在鏈接時就會對每個目標文件的地址進行調整。這樣就可以保證地址不會重複。因爲被加上了起始地址,所以符號在自身文件中的實際地址就不對了,需要再用一張地址重定向表記錄符號相對自身文件的地址。

目標文件的類型:

可重定位文件(.o、.obj文件):其中包含有適合於其它目標文件鏈接來創建一個可執行的或者共享的目標文件的代碼和數據。每個cpp會被編譯成一個.o文件
共享的目標文件(庫文件):這種文件存放了適合於在兩種上下文裏鏈接的代碼和數據。
第一種是鏈接程序(靜態庫)可把它與其它可重定位文件及共享的目標文件一起處理來創建另一個目標文件靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或 者一組相關函數的代碼
第二種是動態鏈接程序(動態庫)將它與另一個可執行文件及其它的共享目標文件結合到一起,創建一個進程映象動態鏈接庫在程序執行時才被調用
可執行文件 一個可以被操作系統創建一個進程來執行之的文件

.o文件在編譯後就能獲得,但是庫文件、可執行文件都需要在鏈接後才能獲得

編譯過程

1.編譯預處理: 讀取源程序,對其中的僞指令(以#開頭的指令)和特殊符號進行處理。
(1)宏定義指令,如# define Name TokenString,#undef等
(2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。
(3)頭文件包含指令,如#include "FileName"或者#include <FileName>等。
(4)特殊符號,預編譯程序可以識別一些特殊的符號。

2.編譯,優化階段:經過預編譯得到的輸出文件中,將只有常量。如數字、字符串、變量的定義,以及關鍵字,如main,if,else,for,while,{,},+,-,*,\,等等。預編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間代碼表示或彙編代碼。 優化一部分是對中間代碼的優化。這種優化不依賴於具體的計算機。另一種優化則主要針對目標代碼的生成而進行的。

3.彙編過程:彙編過程實際上指把彙編語言代碼翻譯成目標機器指令的過程。生成目標文件(.o文件、.obj文件)。

鏈接過程

鏈接:鏈接程序的主要工作就是將有關的目標文件(庫文件、.o文件)彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成爲一個能夠被操作系統裝入執行的統一整體。
具體工作: 當鏈接器進行鏈接的時候,首先決定各個目標文件在最終可執行文件裏的位置。然後訪問所有目標文件的地址重定義表,對其中記錄的地址進行重定向(加上一個偏移量,即該編譯單元在可執行文件上的起始地址)。然後遍歷所有目標文件的未解決符號表,並且在所有的導出符號表裏查找匹配的符號,並在未解決符號表中所記錄的位置上填寫實現地址。最後把所有的目標文件的內容寫在各自的位置上,再作一些另的工作,就生成一個可執行文件。

鏈接方式

靜態鏈接函數的代碼將從其所在地靜態鏈接庫中被拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中
動態鏈接:函數的代碼被放到稱作是動態鏈接庫或共享對象的某個目標文件中。鏈接程序此時所作的只是在最終的可執行程序中記錄下共享對象的名字以及其它少量的登記信息。在此可執行文件被執行時,動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間。動態鏈接程序將根據可執行程序中記錄的信息找到相應的函數代碼。

編譯過程

C/C++中提供的一些特性

extern:這就是告訴編譯器,這個變量或函數在別的編譯單元裏定義了,也就是要把這個符號放到未解決符號表裏面去(外部鏈接)。
默認鏈接屬性:對於函數和變量,默認鏈接是外部鏈接,對於const變量,默認內部鏈接
外部鏈接的利弊:外部鏈接的符號在整個程序範圍內都是可以使用的,這就要求其他編譯單元不能導出相同的符號(不然就會報 duplicated external symbols)。

頭文件裏一般只可以有聲明不能有定義:頭文件可以被多個編譯單元包含,如果頭文件裏面有定義的話,那麼每個包含這頭文件的編譯單元都會對同一個符號進行定義,如果該符號爲外部鏈接,則會導致duplicated external symbols鏈接錯誤。
頭文件中的inline 函數可以被多個cpp包含而不造成符號衝突:因爲它會被直接嵌入到調用的地方,內部聯結不形成外部符號,對外不可見。