Qual è la durata di una variabile statica in una funzione C ++?


373

Se una variabile viene dichiarata come staticnell'ambito di una funzione, viene inizializzata una sola volta e mantiene il suo valore tra le chiamate di funzione. Qual è esattamente la sua vita? Quando vengono chiamati il ​​suo costruttore e distruttore?

void foo() 
{ 
    static string plonk = "When will I die?";
}

Risposte:


257

La durata delle staticvariabili di funzione inizia la prima volta [0] il flusso del programma incontra la dichiarazione e termina al termine del programma. Ciò significa che il tempo di esecuzione deve eseguire alcuni libri per poterlo distruggere solo se è stato effettivamente costruito.

Inoltre, poiché lo standard afferma che i distruttori di oggetti statici devono funzionare nell'ordine inverso rispetto al completamento della loro costruzione [1] , e l'ordine di costruzione può dipendere dall'esecuzione specifica del programma, l'ordine di costruzione deve essere preso in considerazione .

Esempio

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

Produzione:

C:> sample.exe
Creato in foo
Distrutto in foo

C:> sample.exe 1
Creato in se
Creato in foo
Distrutto in foo
Distrutto in if

C:> sample.exe 1 2
Creato in foo
Creato se
distrutto se
distrutto in foo

[0]Dal momento che C ++ 98 [2] non ha alcun riferimento a più thread come questo si comporterà in un ambiente multi-thread non è specificato e può essere problematico come menziona Roddy .

[1] Sezione C ++ 98 3.6.3.1 [basic.start.term]

[2]In C ++ 11 le statiche sono inizializzate in modo thread-safe, questo è anche noto come Magic Statics .


2
Per i tipi semplici senza effetti collaterali c'tor / d'tor, è una semplice ottimizzazione inizializzarli allo stesso modo dei tipi semplici globali. Questo evita i problemi di ramificazione, bandiera e ordine di distruzione. Questo non vuol dire che la loro vita sia diversa.
John McFarlane,

1
Se la funzione può essere chiamata da più thread, significa che è necessario assicurarsi che le dichiarazioni statiche debbano essere protette da un mutex in C ++ 98 ??
codice alleato

1
I "distruttori" di oggetti globali devono funzionare nell'ordine inverso rispetto al completamento della loro costruzione "non si applica qui, poiché questi oggetti non sono globali. L'ordine di distruzione dei locali con durata della memorizzazione statica o dei thread è considerevolmente più complicato del LIFO puro, vedere la sezione 3.6.3[basic.start.term]
Ben Voigt,

2
La frase "al termine del programma" non è strettamente corretta. Che dire delle statistiche nelle DLL di Windows che vengono caricate e scaricate in modo dinamico? Ovviamente lo standard C ++ non si occupa affatto degli assembly (sarebbe bello se lo facesse), ma un chiarimento su ciò che lo standard dice qui sarebbe buono. Se fosse inclusa la frase "al termine del programma", tecnicamente renderebbe non conforme qualsiasi implementazione di C ++ con assembly scaricati dinamicamente.
Roger Sanders,

2
@Motti Non credo che lo standard consenta esplicitamente le librerie dinamiche, ma fino ad ora non credevo che ci fosse qualcosa di specifico nello standard in contrasto con la sua implementazione. Ovviamente, a rigor di termini il linguaggio qui non afferma che gli oggetti statici non possono essere distrutti prima con altri mezzi, solo che devono essere distrutti quando ritornano da main o chiamando std :: exit. Una linea piuttosto sottile anche se penso.
Roger Sanders,

125

Motti ha ragione sull'ordine, ma ci sono alcune altre cose da considerare:

I compilatori utilizzano in genere una variabile di flag nascosta per indicare se le statistiche locali sono già state inizializzate e questo flag viene controllato su ogni voce della funzione. Ovviamente si tratta di un piccolo successo in termini di prestazioni, ma ciò che più preoccupa è che questo flag non è garantito per essere thread-safe.

Se disponi di una statica locale come sopra e fooviene chiamata da più thread, potresti avere condizioni di competizione che potrebbero plonkessere inizializzate in modo errato o anche più volte. Inoltre, in questo caso plonkpuò essere distrutto da un thread diverso da quello che lo ha costruito.

Nonostante ciò che dice lo standard, sarei molto cauto sull'ordine reale della distruzione statica locale, perché è possibile che tu possa inconsapevolmente fare affidamento su un essere statico ancora valido dopo che è stato distrutto, e questo è davvero difficile da rintracciare.


68
C ++ 0x richiede che l'inizializzazione statica sia thread-safe. Quindi diffidare, ma le cose andranno solo meglio.
deft_code

I problemi con gli ordini di distruzione possono essere evitati con una piccola politica. gli oggetti statici / globali (singoli, ecc.) non devono accedere ad altri oggetti statici nei loro corpi di metodo. Esse hanno accesso solo nei costruttori in cui un riferimento / puntatore può essere memorizzato per un accesso successivo nei metodi. Questo non è perfetto, ma dovrebbe risolvere 99 dei casi e i casi che non rileva sono ovviamente sospetti e dovrebbero essere presi in una revisione del codice. Questa non è ancora una soluzione perfetta poiché la politica non può essere applicata nella lingua
deft_code

Sono un po 'noob, ma perché questa politica non può essere applicata nella lingua?
cjcurrie,

9
Dal C ++ 11, questo non è più un problema. La risposta di Motti viene aggiornata in base a ciò.
Nilanjan Basu,

10

Le spiegazioni esistenti non sono realmente complete senza la regola effettiva dello standard, trovata in 6.7:

L'inizializzazione zero di tutte le variabili con ambito di blocco con durata dell'archiviazione statica o durata dell'archiviazione del thread viene eseguita prima di qualsiasi altra inizializzazione. L'inizializzazione costante di un'entità con ambito di blocco con durata di memorizzazione statica, se applicabile, viene eseguita prima che il blocco venga immesso per la prima volta. Un'implementazione può eseguire l'inizializzazione anticipata di altre variabili con ambito di blocco con durata dell'archiviazione statica o thread nelle stesse condizioni in cui è consentita un'implementazione per inizializzare staticamente una variabile con durata dell'archiviazione statica o thread nell'ambito dello spazio dei nomi. Altrimenti una variabile del genere viene inizializzata la prima volta che il controllo passa attraverso la sua dichiarazione; tale variabile è considerata inizializzata al completamento della sua inizializzazione. Se l'inizializzazione termina generando un'eccezione, l'inizializzazione non è completa, quindi verrà riprovata la prossima volta che il controllo entra nella dichiarazione. Se il controllo inserisce la dichiarazione contemporaneamente mentre la variabile viene inizializzata, l'esecuzione simultanea deve attendere il completamento dell'inizializzazione. Se il controllo reinserisce la dichiarazione in modo ricorsivo durante l'inizializzazione della variabile, il comportamento non è definito.


8

FWIW, Codegear C ++ Builder non si distrugge nell'ordine previsto secondo lo standard.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... che è un altro motivo per non fare affidamento sull'ordine di distruzione!


57
Non è un buon argomento. Direi che questo è più un argomento per non usare questo compilatore.
Martin York,

26
Hmm. Se sei interessato a produrre codice portatile del mondo reale, anziché solo codice teoricamente portatile, penso che sia utile sapere quali aree della lingua possono causare problemi. Sarei sorpreso se C ++ Builder fosse unico nel non gestirlo.
Roddy,

17
Concordo, tranne per il fatto che lo definisco "ciò che i compilatori causano problemi e in quali aree del linguaggio lo fanno" ;-P
Steve Jessop,

0

Le variabili statiche entrano in gioco una volta avviata l'esecuzione del programma e rimangono disponibili fino al termine dell'esecuzione del programma.

Le variabili statiche vengono create nel segmento dati della memoria .


questo non è vero per le variabili nell'ambito della funzione
awerries
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.