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++ 標準不提供一個一致的方式來指定使用者提供的全域初始化運算式之翻譯單位之間的相對順序。 不過,由於 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
檔案