共用方式為


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 檔案