Udostępnij za pośrednictwem


Inicjalizacja CRT

W tym artykule opisano sposób inicjowania stanu globalnego CRT w kodzie natywnym.

Domyślnie konsolidator zawiera bibliotekę CRT, która udostępnia własny kod startowy. Ten kod startowy inicjuje bibliotekę CRT, wywołuje globalne inicjatory, a następnie wywołuje funkcję udostępnioną przez main użytkownika dla aplikacji konsoli.

Jednak nie jest to zalecane, aby skorzystać z zachowania konsolidatora specyficznego dla firmy Microsoft w celu wstawienia własnych globalnych inicjatorów w określonej kolejności. Ten kod nie jest przenośny i zawiera pewne ważne zastrzeżenia.

Inicjowanie obiektu globalnego

Rozważmy następujący kod C++ (język C nie zezwala na ten kod, ponieważ nie zezwala na wywołanie funkcji w wyrażeniu stałym).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

Zgodnie ze standardem func() C/C++ należy wywołać metodę przed main() wykonaniem. Ale kto to nazywa?

Jednym ze sposobów określenia wywołującego jest ustawienie punktu przerwania w func()programie , debugowanie aplikacji i badanie stosu. Jest to możliwe, ponieważ kod źródłowy CRT jest dołączony do programu Visual Studio.

Podczas przeglądania funkcji na stosie zobaczysz, że narzędzie CRT wywołuje listę wskaźników funkcji. Te funkcje są podobne do func(), lub konstruktorów dla wystąpień klas.

Narzędzie CRT pobiera listę wskaźników funkcji z kompilatora języka Microsoft C++. Gdy kompilator widzi globalny inicjator, generuje dynamiczny inicjator w .CRT$XCU sekcji, w której CRT znajduje się nazwa sekcji i XCU jest nazwą grupy. Aby uzyskać listę dynamicznych inicjatorów, uruchom polecenie dumpbin /all main.obj, a następnie wyszukaj sekcję .CRT$XCU . Polecenie ma zastosowanie tylko wtedy, gdy main.cpp jest kompilowany jako plik C++, a nie plik C. Powinien być podobny do tego przykładu:

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 definiuje dwa wskaźniki:

  • __xc_a w systemie .CRT$XCA
  • __xc_z w systemie .CRT$XCZ

Żadna grupa nie ma żadnych innych symboli zdefiniowanych z wyjątkiem __xc_a i __xc_z.

Teraz, gdy konsolidator odczytuje różne .CRT podsekcje (część po ), $łączy je w jednej sekcji i porządkuje je alfabetycznie. Oznacza to, że globalne inicjatory zdefiniowane przez użytkownika (które kompilator Microsoft C++ umieszcza ) .CRT$XCUzawsze pojawiają się po .CRT$XCA i przed .CRT$XCZ.

Sekcja powinna przypominać ten przykład:

.CRT$XCA
            __xc_a
.CRT$XCU
            Pointer to Global Initializer 1
            Pointer to Global Initializer 2
.CRT$XCZ
            __xc_z

Biblioteka CRT używa metody __xc_a i __xc_z , aby określić początek i koniec globalnej listy inicjatorów ze względu na sposób, w jaki są one ułożone w pamięci po załadowaniu obrazu.

Funkcje konsolidatora na potrzeby inicjowania

Standard C++ nie zapewnia zgodnego sposobu określania kolejności względnej między jednostkami tłumaczenia dla globalnego inicjatora dostarczonego przez użytkownika. Jednak ponieważ konsolidator firmy Microsoft porządkuje .CRT podsekcje alfabetycznie, można skorzystać z tego zamówienia w celu określenia kolejności inicjowania. Nie zalecamy tej techniki specyficznej dla firmy Microsoft i może to spowodować przerwanie w przyszłej wersji. Udokumentowaliśmy to tylko, aby uniemożliwić tworzenie kodu, który jest uszkodzony w trudnych do zdiagnozowania sposobów.

Aby zapobiec problemom w kodzie, począwszy od programu Visual Studio 2019 w wersji 16.11, dodaliśmy dwa nowe ostrzeżenia: C5247 i C5248. Włącz te ostrzeżenia, aby wykrywać problemy podczas tworzenia własnych inicjatorów.

Inicjatory można dodawać do nieużywanych nazw sekcji zarezerwowanych, aby utworzyć je w określonej kolejności względnej do kompilatora wygenerowanych dynamicznych inicjatorów:

#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;

Nazwy .CRT$XCT i .CRT$XCV nie są obecnie używane przez kompilator lub bibliotekę CRT, ale nie ma gwarancji, że pozostaną nieużywane w przyszłości. Zmienne mogą być nadal optymalizowane przez kompilator. Przed wdrożeniem tej techniki należy wziąć pod uwagę potencjalne problemy z inżynierią, konserwacją i przenośnością.

Zobacz też

_initterm, _initterm_e
Pliki C runtime (CRT) i C++ Standard Library (STL) .lib