Inizializzazione CRT

Questo articolo descrive in che modo CRT inizializza lo stato globale nel codice nativo.

Per impostazione predefinita, il linker include la libreria CRT che fornisce il proprio codice di avvio. Questo codice di avvio inizializza la libreria CRT, chiama gli inizializzatori globali e quindi chiama la funzione main fornita dall'utente per le applicazioni console.

È possibile, anche se non consigliato, sfruttare il comportamento del linker specifico di Microsoft per inserire inizializzatori globali in un ordine specifico. Questo codice non è portabile e include alcune avvertenze importanti.

Inizializzazione di un oggetto globale

Si consideri il codice C++ seguente (C non consente questo codice perché non consente una chiamata di funzione in un'espressione costante).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

Secondo lo standard C/C++, func() deve essere chiamato prima che main() venga eseguito. Ma chi lo chiama?

Un modo per determinare il chiamante consiste nell'impostare un punto di interruzione in func(), eseguire il debug dell'applicazione ed esaminare lo stack. È possibile perché il codice sorgente CRT è incluso in Visual Studio.

Quando si esplorano le funzioni nello stack, si noterà che CRT chiama un elenco di puntatori a funzione. Queste funzioni sono simili a func(), o costruttori per le istanze di classe.

Il CRT ottiene l'elenco dei puntatori a funzione dal compilatore Microsoft C++. Quando il compilatore vede un inizializzatore globale, genera un inizializzatore dinamico nella sezione dove CRT è il nome della .CRT$XCU sezione e XCU è il nome del gruppo. Per ottenere un elenco di inizializzatori dinamici, eseguire il comando dumpbin /all main.obje quindi cercare la .CRT$XCU sezione . Il comando si applica solo quando main.cpp viene compilato come file C++, non come file C. Dovrebbe essere simile a questo esempio:

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 definisce due puntatori:

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

Nessuno dei due gruppi ha altri simboli definiti ad eccezione __xc_a di e __xc_z.

Ora, quando il linker legge varie .CRT sottosezioni (la parte dopo ), $le combina in una sezione e le ordina alfabeticamente. Significa che gli inizializzatori globali definiti dall'utente (che il compilatore Microsoft C++ inserisce .CRT$XCU) vengono sempre dopo .CRT$XCA e prima .CRT$XCZdi .

La sezione dovrebbe essere simile a questo esempio:

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

La libreria CRT usa sia __xc_a che __xc_z per determinare l'inizio e la fine dell'elenco di inizializzatori globali a causa del modo in cui sono disposti in memoria dopo il caricamento dell'immagine.

Funzionalità del linker per l'inizializzazione

Lo standard C++ non fornisce un modo conforme per specificare l'ordine relativo tra le unità di conversione per un inizializzatore globale fornito dall'utente. Tuttavia, poiché il linker Microsoft ordina in ordine alfabetico le .CRT sottosezioni, è possibile sfruttare questo ordinamento per specificare l'ordine di inizializzazione. Questa tecnica specifica di Microsoft non è consigliata e potrebbe interrompersi in una versione futura. È stato documentato solo per impedire la creazione di codice interrotto in modi difficili da diagnosticare.

Per evitare problemi nel codice, a partire da Visual Studio 2019 versione 16.11, sono stati aggiunti due nuovi avvisi disattivati per impostazione predefinita: C5247 e C5248. Abilitare questi avvisi per rilevare i problemi durante la creazione di inizializzatori personalizzati.

È possibile aggiungere inizializzatori ai nomi di sezione riservati inutilizzati per crearli in un ordine relativo specifico agli inizializzatori dinamici generati dal compilatore:

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

I nomi .CRT$XCT e .CRT$XCV non vengono usati dal compilatore o dalla libreria CRT, ma non c'è alcuna garanzia che rimangano inutilizzati in futuro. Inoltre, le variabili potrebbero essere ancora ottimizzate dal compilatore. Prendere in considerazione i potenziali problemi di progettazione, manutenzione e portabilità prima di adottare questa tecnica.

Vedi anche

_initterm, _initterm_e
File C Runtime (CRT) e C++ Standard Library (STL) .lib