Domanda

C ++ garantisce che le variabili in un'unità di compilazione (file .cpp) siano inizializzate in ordine di dichiarazione. Per il numero di unità di compilazione questa regola funziona separatamente per ciascuna (intendo variabili statiche al di fuori delle classi).

Ma l'ordine di inizializzazione delle variabili non è definito tra le diverse unità di compilazione.

Dove posso vedere alcune spiegazioni su questo ordine per gcc e MSVC (so che fare affidamento su questa è una pessima idea - è solo per capire i problemi che potremmo avere con il codice legacy quando ci spostiamo nel nuovo GCC major e sistema operativo diverso)?

È stato utile?

Soluzione

Come dici tu, l'ordine non è definito tra le diverse unità di compilazione.

All'interno della stessa unità di compilazione l'ordine è ben definito: lo stesso ordine della definizione.

Questo perché non è stato risolto a livello di lingua ma a livello di linker. Quindi devi davvero controllare la documentazione del linker. Anche se dubito davvero che ciò possa aiutare in alcun modo utile.

Per gcc: controlla vecchio

Ho scoperto che anche cambiando l'ordine degli oggetti i file collegati possono cambiare l'ordine di inizializzazione. Quindi non devi preoccuparti solo del tuo linker, ma di come il linker viene invocato dal tuo sistema di compilazione. Persino tentare di risolvere il problema è praticamente un antipasto.

Questo è generalmente un problema solo quando si inizializzano i globuli che si riferiscono a vicenda durante la propria inizializzazione (quindi influisce solo sugli oggetti con i costruttori).

Esistono tecniche per aggirare il problema.

  • Inizializzazione lenta.
  • Schwarz Counter
  • Inserisci tutte le variabili globali complesse all'interno della stessa unità di compilazione.

  • Nota 1: globali:
    Usato vagamente per fare riferimento a variabili di durata della memoria statica che sono potenzialmente inizializzate prima di main () .
  • Nota 2: potenzialmente
    In generale, prevediamo che le variabili di durata della memoria statica vengano inizializzate prima di main, ma il compilatore può posticipare l'inizializzazione in alcune situazioni (le regole sono complesse, vedi standard per i dettagli).

Altri suggerimenti

Mi aspetto che l'ordine di costruzione tra i moduli sia principalmente una funzione di quale ordine si passano gli oggetti al linker.

Tuttavia, GCC ti consente di utilizzare init_priority per specificare esplicitamente l'ordinamento per i medici globali:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

genera "ABC" come ti aspetteresti, ma

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");

genera "BAC".

Dato che sai già che non dovresti fare affidamento su queste informazioni se non in caso di assoluta necessità, ecco che arriva. La mia osservazione generale su varie toolchain (MSVC, gcc / ld, clang / llvm, ecc.) È che l'ordine in cui i file oggetto vengono passati al linker è l'ordine in cui verranno inizializzati.

Ci sono eccezioni a questo, e io non pretendo di tutti, ma qui ci sono quelli che mi sono imbattuto in me stesso:

1) Le versioni GCC precedenti alla 4.7 vengono effettivamente inizializzate nell'ordine inverso della linea di collegamento. Questo biglietto in GCC è quando il cambiamento è avvenuto e si è rotto molto di programmi che dipendevano dall'ordine di inizializzazione (incluso il mio!).

2) In GCC e Clang, l'utilizzo del costruttore la priorità della funzione può modificare l'ordine di inizializzazione. Nota che questo vale solo per le funzioni che sono dichiarate come "costruttori" (cioè dovrebbero essere eseguiti esattamente come un costruttore globale di oggetti). Ho provato a usare priorità come questa e ho scoperto che anche con la massima priorità su una funzione di costruzione, tutti i costruttori senza priorità (ad es. Oggetti globali normali, funzioni di costruzione senza priorità) verranno inizializzati prima . In altre parole, la priorità è relativa solo ad altre funzioni con priorità, ma i veri cittadini di prima classe sono quelli senza priorità. A peggiorare le cose, questa regola è effettivamente l'opposto in GCC prima della 4.7 a causa del punto (1) sopra.

3) Su Windows, esiste una funzione entry-point (DLL) della libreria condivisa molto accurata e utile chiamata DllMain () , che se definito, verrà eseguito con il parametro " fdwReason " uguale a DLL_PROCESS_ATTACH direttamente dopo che tutti i dati globali sono stati inizializzati e prima l'applicazione che consuma ha la possibilità di chiamare qualsiasi funzione sulla DLL. Ciò è estremamente utile in alcuni casi e non esiste assolutamente un comportamento analogo a su altre piattaforme con GCC o Clang con C o C ++. Il più vicino che troverai è creare una funzione di costruzione con priorità (vedi sopra punto (2)), che non è assolutamente la stessa cosa e non funzionerà per molti dei casi d'uso per cui DllMain () funziona.

4) Se stai usando CMake per generare i tuoi sistemi di build, cosa che faccio spesso, ho scoperto che l'ordine dei file sorgente di input sarà l'ordine dei loro file oggetto risultanti dati al linker. Tuttavia, molte volte l'applicazione / DLL si collega anche in altre librerie, nel qual caso tali librerie si troveranno sulla linea di collegamento dopo i file di origine di input. Se stai cercando di avere uno dei tuoi oggetti globali sia il primissimo da inizializzare, allora sei fortunato e puoi mettere il file sorgente contenente quell'oggetto come il primo nell'elenco dei sorgenti File. Tuttavia, se stai cercando di avere uno molto ultimo da inizializzare (che può replicare efficacemente il comportamento DllMain ()!) Allora puoi fare una chiamata a add_library () con quell'unico file sorgente a produrre una libreria statica e aggiungere la libreria statica risultante come ultima dipendenza del collegamento nella chiamata target_link_libraries () per l'applicazione / DLL. Diffida del fatto che il tuo oggetto globale potrebbe essere ottimizzato in questo caso e puoi usare - whole-archive flag per forzare il linker a non rimuovere simboli inutilizzati per quel file di archivio specifico specifico.

Suggerimento di chiusura

Per conoscere assolutamente l'ordine di inizializzazione risultante dell'applicazione / libreria condivisa collegata, passare --print-map a ld linker e grep per .init_array (o in GCC prima della 4.7, grep per .ctors). Ogni costruttore globale verrà stampato nell'ordine in cui verrà inizializzato e ricorda che l'ordine è opposto in GCC prima del 4.7 (vedi punto (1) sopra).

Il fattore motivante per scrivere questa risposta è che avevo bisogno di conoscere queste informazioni, non avevo altra scelta che fare affidamento sull'ordine di inizializzazione e ho trovato solo poche parti di queste informazioni in altri post SO e forum Internet. Gran parte di ciò è stato appreso attraverso molte sperimentazioni e spero che questo risparmi ad alcune persone il tempo di farlo!

http://www.parashift.com/c++ -faq-lite / ctors.html # faq-10.12 - questo collegamento si sposta. questo uno è più stabile ma dovrai cercarlo in giro.

modifica: osgx ha fornito una migliore link .

Oltre ai commenti di Martin, provenienti da uno sfondo C, penso sempre alle variabili statiche come parte dell'eseguibile del programma, allo spazio incorporato e allocato nel segmento di dati. Pertanto, le variabili statiche possono essere pensate come inizializzate durante il caricamento del programma, prima che venga eseguito qualsiasi codice. L'ordine esatto in cui ciò accade può essere verificato osservando il segmento di dati dell'output del file della mappa dal linker, ma per la maggior parte degli scopi l'inizializzazione è simultanea.

Modifica: a seconda dell'ordine di costruzione degli oggetti statici può essere non portatile e dovrebbe probabilmente essere evitato.

Se vuoi davvero conoscere l'ordine finale, ti consiglio di creare una classe il cui costruttore registra il timestamp corrente e creare diverse istanze statiche della classe in ciascuno dei tuoi file cpp in modo da poter conoscere l'ordine finale di inizializzazione . Assicurati di mettere un po 'di tempo in un'operazione nel costruttore solo in modo da non ottenere lo stesso timestamp per ogni file.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top