CRT 初始化
本文說明 CRT 如何在機器碼中初始化全域狀態。
根據預設,連結器會包含能自行提供起始程式碼的 CRT 程式庫。 此起始程式碼會初始化 CRT 程式庫,呼叫全域初始設定式,然後呼叫由使用者針對主控台應用程式所提供的 main
函式。
雖然不建議這麼做,但建議您利用Microsoft特定鏈接器行為,以特定順序插入您自己的全域初始化表達式。 此程式代碼無法移植,並隨附一些重要的注意事項。
初始化全域物件
請考慮下列C++程序代碼(C 不允許此程式代碼,因為它不允許常數表達式中的函式呼叫)。
int func(void)
{
return 3;
}
int gi = func();
int main()
{
return gi;
}
根據 C/C++ 標準,func()
必須在執行 main()
之前呼叫。 但呼叫它的是誰?
判斷呼叫端的其中一種方式是在 中 func()
設定斷點、對應用程式進行偵錯,以及檢查堆棧。 可能是因為CRT原始程式碼隨附於Visual Studio中。
當您瀏覽堆疊上的函式時,您會看到 CRT 正在呼叫函式指標清單。 這些函式類似於 func()
類別實例的、 或 建構函式。
CRT 會從Microsoft C++編譯程式取得函式指標清單。 當編譯程式看到全域初始化表達式時,它會在 .CRT$XCU
區段中產生動態初始化表達式,其中 CRT
是區段名稱,而 XCU
是組名。 若要取得動態初始化表達式的清單,請執行 命令 dumpbin /all main.obj
,然後搜尋 區 .CRT$XCU
段。 只有在編譯為C++檔案,而不是 C 檔案時 main.cpp
,才會套用命令。 它應該類似下列範例:
SECTION HEADER #6
.CRT$XCU name
0 physical address
0 virtual address
4 size of raw data
1F2 file pointer to raw data (000001F2 to 000001F5)
1F6 file pointer to relocation table
0 file pointer to line numbers
1 number of relocations
0 number of line numbers
40300040 flags
Initialized Data
4 byte align
Read Only
RAW DATA #6
00000000: 00 00 00 00 ....
RELOCATIONS #6
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- -------
00000000 DIR32 00000000 C ??__Egi@@YAXXZ (void __cdecl `dynamic initializer for 'gi''(void))
CRT 會定義兩個指標:
.CRT$XCA
中的__xc_a
.CRT$XCZ
中的__xc_z
除了和 __xc_z
之外,兩個群組都沒有定義__xc_a
任何其他符號。
現在,當連結器讀取各種 .CRT
子區段(之後的部分), $
它會將它們結合在一個區段中,並依字母順序排序。 這表示使用者定義的全域初始化表達式(Microsoft C++編譯程式放入 .CRT$XCU
的初始化表達式一律會在 之後 .CRT$XCA
和之前 .CRT$XCZ
。
區段應該類似下列範例:
.CRT$XCA
__xc_a
.CRT$XCU
Pointer to Global Initializer 1
Pointer to Global Initializer 2
.CRT$XCZ
__xc_z
CRT 連結庫會 __xc_a
使用 和 __xc_z
來判斷全域初始化表達式清單的開始和結尾,因為在載入映射之後,它們配置在記憶體中的方式。
初始化的連結器功能
C++ Standard 不提供一個符合規範的方式,為使用者提供的全域初始化表達式指定跨轉譯單位的相對順序。 不過,由於Microsoft連結器會依字母順序排序 .CRT
子區段,因此可以利用這個順序來指定初始化順序。 我們不建議使用此Microsoft特定技術,而且未來版本可能會中斷。 我們只記錄了它,讓您無法建立以難以診斷的方式中斷的程序代碼。
為了協助防止程式代碼中的問題,從 Visual Studio 2019 16.11 版開始,我們已新增兩個新的關閉預設警告: C5247 和 C5248。 啟用這些警告,以在建立您自己的初始化表達式時偵測問題。
您可以將初始化表示式新增至未使用的保留區段名稱,以特定相對順序建立初始化運算式給編譯程式產生的動態初始化表示式:
#pragma section(".CRT$XCT", read)
// 'i1' is guaranteed to be called before any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCT")) type i1 = f;
#pragma section(".CRT$XCV", read)
// 'i2' is guaranteed to be called after any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCV")) type i2 = f;
編譯程式或CRT連結庫目前未使用的名稱 .CRT$XCT
和 .CRT$XCV
,但無法保證它們在未來仍會保持未使用。 而且,您的變數仍然可以由編譯程序優化。 在採用這項技術之前,請先考慮潛在的工程、維護和可移植性問題。
另請參閱
_initterm, _initterm_e
C 執行時間 (CRT) 和 C++ 標準連結庫 (STL) .lib
檔案