CRT-Initialisierung

In diesem Artikel wird beschrieben, wie der crT den globalen Zustand im systemeigenen Code initialisiert.

Standardmäßig enthält der Linker die CRT-Bibliothek, die einen eigenen Startcode bereitstellt. Dieser Startcode initialisiert die CRT-Bibliothek, ruft globale Initialisierer auf und ruft dann die vom Benutzer bereitgestellte main-Funktion für Konsolenanwendungen auf.

Es ist jedoch nicht empfehlenswert, das Microsoft-spezifische Linkerverhalten zu nutzen, um eigene globale Initialisierer in eine bestimmte Reihenfolge einzufügen. Dieser Code ist nicht portierbar und enthält einige wichtige Einschränkungen.

Initialisieren eines globalen Objekts

Berücksichtigen Sie den folgenden C++-Code (C lässt diesen Code nicht zu, da ein Funktionsaufruf in einem konstanten Ausdruck nicht zulässig ist).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

Laut dem C/C++-Standard muss func() aufgerufen werden, bevor main() ausgeführt wird. Doch wer ruft diese Funktion auf?

Eine Möglichkeit zum Ermitteln des Aufrufers besteht darin, einen Haltepunkt func()festzulegen, die Anwendung zu debuggen und den Stapel zu untersuchen. Es ist möglich, dass der CRT-Quellcode in Visual Studio enthalten ist.

Wenn Sie die Funktionen im Stapel durchsuchen, sehen Sie, dass das CRT eine Liste von Funktionszeigern aufruft. Diese Funktionen ähneln func()oder Konstruktoren für Klasseninstanzen.

Die CRT ruft die Liste der Funktionszeiger aus dem Microsoft C++-Compiler ab. Wenn der Compiler einen globalen Initialisierer sieht, wird ein dynamischer Initialisierer im Abschnitt generiert, CRT in dem .CRT$XCU es sich um den Abschnittsnamen und XCU den Gruppennamen handelt. Um eine Liste der dynamischen Initialisierer abzurufen, führen Sie den Befehl dumpbin /all main.objaus, und durchsuchen Sie dann den .CRT$XCU Abschnitt. Der Befehl gilt nur, wenn main.cpp er als C++-Datei und nicht als C-Datei kompiliert wird. Es sollte ähnlich wie in diesem Beispiel sein:

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 definiert zwei Zeiger:

  • __xc_a in .CRT$XCA
  • __xc_z in .CRT$XCZ

Keine Gruppe hat andere Symbole definiert, außer __xc_a und __xc_z.

Wenn der Linker nun verschiedene .CRT Unterabschnitte (den Teil hinter dem $) liest, kombiniert er sie in einem Abschnitt und sortiert sie alphabetisch. Dies bedeutet, dass die benutzerdefinierten globalen Initialisierer (die der Microsoft C++-Compiler eingibt .CRT$XCU) immer danach .CRT$XCA und vor .CRT$XCZ.

Der Abschnitt sollte diesem Beispiel ähneln:

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

Die CRT-Bibliothek verwendet sowohl das Start- __xc_a __xc_z als auch das Ende der globalen Initialisierungsliste aufgrund der Art und Weise, in der sie im Arbeitsspeicher angeordnet sind, nachdem das Bild geladen wurde.

Linkerfeatures für die Initialisierung

Der C++-Standard bietet keine konforme Möglichkeit zum Angeben der relativen Reihenfolge für übersetzungseinheiten für einen vom Benutzer bereitgestellten globalen Initialisierer. Da der Microsoft-Linker die .CRT Unterabschnitte alphabetisch sortiert, ist es jedoch möglich, diese Sortierung zum Angeben der Initialisierungsreihenfolge zu nutzen. Wir empfehlen diese Microsoft-spezifische Technik nicht, und es kann in einer zukünftigen Version brechen. Wir haben es dokumentiert, um Sie daran zu hindern, Code zu erstellen, der auf schwer zu diagnostizierende Weise unterbrochen ist.

Um Probleme in Ihrem Code zu vermeiden, haben wir ab Visual Studio 2019, Version 16.11, standardmäßig zwei neue Warnungen hinzugefügt: C5247 und C5248. Aktivieren Sie diese Warnungen, um Probleme beim Erstellen eigener Initialisierer zu erkennen.

Sie können Initialisierer zu nicht verwendeten reservierten Abschnittsnamen hinzufügen, um sie in einer bestimmten relativen Reihenfolge zum Compiler generierten dynamischen Initialisierern zu erstellen:

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

Die Namen .CRT$XCT und .CRT$XCV werden derzeit nicht vom Compiler oder der CRT-Bibliothek verwendet, aber es gibt keine Garantie, dass sie in Zukunft nicht verwendet werden Standard. Und Ihre Variablen könnten weiterhin vom Compiler optimiert werden. Berücksichtigen Sie die potenziellen Probleme im Bereich Engineering, Standard intensität und Portabilität, bevor Sie diese Technik einführen.

Siehe auch

_initterm, _initterm_e
C-Runtime-Dateien (CRT) und C++ Standard Library (STL) .lib