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.obj
e 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$XCZ
di .
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