要更深入了解C++, 必需要知道一個程序從開始到結束都干了些什么, 怎么干的。
所以我從C++編譯到執行過程,解析下程序是怎么跑的。
首先,初略的說一下之前C++的編譯過程。C++編譯過程包含預編譯-》匯編-》編譯-》鏈接。稱為一個可運行文件。(Windows平台下為.exe文件)。
預編譯主要展開包括的頭文件,宏定義等操作。比如一個簡單的main程序,編譯預編譯后,的文件對照。
能夠看到里面的宏已經被去掉了。
假設定了那個宏。那么宏里面的內容也會顯示出來。頭文件也是。假設你包括了你一個.h 文件,那么整個.h文件會包括進來。
匯編過程,就是把已經預編譯的文件編譯成匯編代碼的過程。整個過程會包括語法,詞法的分析,和一些優化操作。
編譯過程事實上是跟匯編能夠合成一個階段,變成目標代碼。也就是二進制文件。
鏈接過程是將單個編譯后的文件鏈接成一個可運行程序。前面的預編譯、匯編、編譯都是正對單個文件,以一個文件為一個編譯單元,而鏈接則是將全部關聯到的編譯后單元文件和應用的到庫文件。進行一次鏈接處理,之前編譯過的文件 假設實用到其它文件中面定義到的函數。全局變量。在這個過程中都會進行解析。
首先看看編譯后的文件樣子(已VS2012編譯后的OBJ文件為樣例。不同編譯器 樣式可能會不同。
)
編譯前的文件
#include "Car.h"
int main(int argc, char* argv[])
{
Car* p = new Car();
delete p;
return 1;
}
編譯后的樣子(因為編譯后的文件 信息太多 僅僅貼出里面未解析符號部分。
)
UNDEF:00002DC4 ; int __thiscall Car::Car(Car *__hidden this)
UNDEF:00002DC4 extrn ?
0Car@@QAE@XZ:near ; CODE XREF: _main+63p
UNDEF:00002DC8 ; int __thiscall Car::~Car(Car *__hidden this)
UNDEF:00002DC8 extrn ??
1Car@@QAE@XZ:near
UNDEF:00002DC8 ; CODE XREF: Car::`scalar deleting destructor'(uint)+26p
UNDEF:00002DCC ; __fastcall _RTC_CheckStackVars(x, x)
UNDEF:00002DCC extrn @_RTC_CheckStackVars@8:near
UNDEF:00002DCC ; CODE XREF: std::_String_alloc<0,std::_String_base_types<char,std::allocator<char>>>::_Alloc_proxy(void)+68p
UNDEF:00002DCC ; $LN19+72p ...
UNDEF:00002DD0 ; __fastcall __security_check_cookie(x)
UNDEF:00002DD0 extrn @__security_check_cookie@4:near
UNDEF:00002DD0 ; CODE XREF: __ehhandler$??$construct@PADAAPAD@?$allocator@D@std@@QAEXPAPADAAPAD@Z+Fp
UNDEF:00002DD0 ; __ehhandler$??
$construct@U_Container_proxy@std@@U12@@?$allocator@U_Container_proxy@std@@@std@@QAEXPAU_Container_proxy@1@$$QAU21@@Z+Fp ...
UNDEF:00002DD4 ; __stdcall _CxxThrowException(x, x)
編譯后的文件用(用反匯編成匯編代碼查看) 當中實現函數會變成一堆匯編指令。而那些引用到的在其它文件中面實現的函數將會變成一個特點的符號(如上面中的調用Car類的構造函數 extrn ??0Car@@QAE@XZ:near)這些符號稱做為解析的符號。表示在鏈接的時候須要被解析。
符號的生成名稱詳細跟編譯器有關,可是會保證一個類的某個函數名稱在同一個編譯里面必須是唯一的,由於我們在預編譯階段已經把Car.h包括進來所以編譯器能正確生成這個函數的名字。然后在鏈接的時候 會找到改名字的函數,把此標識名字替換為函數的地址。這樣就實現的鏈接。
在符號解析(symbol resolution)階段,鏈接器依照全部目標文件和庫文件出如今命令行中的順序從左至右依次掃描它們。在此期間它要維護若干個集合:(1)集合E是將被合並到一起組成可運行文件的全部目標文件集合。(2)集合U是未解析符號(unresolved symbols,比方已經被引用可是還未被定義的符號)的集合。(3)集合D是全部之前已被增加到E的目標文件定義的符號集合。一開始,E、U、D都是空的。
(1): 對命令行中的每個輸入文件f,鏈接器確定它是目標文件還是庫文件,假設它是目標文件。就把f增加到E,並把f中未解析的符號和已定義的符號分別增加到U、D集合中。然后處理下一個輸入文件。
(2): 假設f是一個庫文件,鏈接器會嘗試把U中的全部未解析符號與f中各目標模塊定義的符號進行匹配。假設某個目標模塊m定義了一個U中的未解析符號,那么就把 m增加到E中,並把m中未解析的符號和已定義的符號分別增加到U、D集合中。不斷地對f中的全部目標模塊反復這個過程直至到達一個不動點(fixed point),此時U和D不再變化。而那些未增加到E中的f里的目標模塊就被簡單地丟棄,鏈接器繼續處理下一輸入文件。
(3): 假設處理過程中往D增加一個已存在的符號,或者當掃描全然部輸入文件時U非空。鏈接器報錯並停止動作。否則,它把E中的全部目標文件合並在一起生成可運行文件。