Quelle est la durée de vie d'une variable statique dans une fonction C ++?

373

Si une variable est déclarée comme staticdans la portée d'une fonction, elle n'est initialisée qu'une seule fois et conserve sa valeur entre les appels de fonction. Quelle est exactement sa durée de vie? Quand son constructeur et son destructeur sont-ils appelés?

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

Réponses:

257

La durée de vie des staticvariables de fonction commence la première fois [0] que le flux de programme rencontre la déclaration et se termine à la fin du programme. Cela signifie que le run-time doit effectuer une comptabilité afin de le détruire uniquement s'il a été réellement construit.

De plus, comme la norme stipule que les destructeurs d'objets statiques doivent s'exécuter dans l'ordre inverse de l'achèvement de leur construction [1] et que l'ordre de construction peut dépendre de l'exécution spécifique du programme, l'ordre de construction doit être pris en compte .

Exemple

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);
}

Production:

C:> sample.exe
Créé dans foo
Détruit dans foo

C:> sample.exe 1
Créé dans if
Créé dans foo
Détruit dans foo
Détruit dans if

C:> sample.exe 1 2
Créé dans foo
Créé dans if
Destroyed dans if
Destroyed in foo

[0]Étant donné que C ++ 98 [2] n'a aucune référence à plusieurs threads, la façon dont cela se comportera dans un environnement multithread n'est pas spécifiée et peut être problématique comme le mentionne Roddy .

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

[2]En C ++ 11, les statiques sont initialisées de manière sécurisée pour les threads, ce qui est également connu sous le nom de Magic Statics .

Motti
la source
2
Pour les types simples sans effets secondaires c'tor / d'tor, il est simple de les initialiser de la même manière que les types simples globaux. Cela évite les problèmes de branchement, de drapeau et d'ordre de destruction. Cela ne veut pas dire que leur vie est différente.
John McFarlane
1
Si la fonction peut être appelée par plusieurs threads, cela signifie-t-il que vous devez vous assurer que les déclarations statiques doivent être protégées par un mutex en C ++ 98 ??
allyourcode
1
«les destructeurs d'objets globaux doivent fonctionner dans l'ordre inverse de l'achèvement de leur construction» ne s'applique pas ici, car ces objets ne sont pas globaux. L'ordre de destruction des sections locales avec une durée de stockage statique ou de thread est beaucoup plus compliqué que le LIFO pur, voir la section 3.6.3[basic.start.term]
Ben Voigt
2
L'expression «à la fin du programme» n'est pas strictement correcte. Qu'en est-il de la statique dans les DLL Windows qui sont chargées et déchargées dynamiquement? De toute évidence, la norme C ++ ne traite pas du tout des assemblages (ce serait bien si c'était le cas), mais une clarification concernant exactement ce que la norme dit ici serait bonne. Si l'expression "à la fin du programme" était incluse, cela rendrait techniquement toute implémentation de C ++ avec des assemblages dynamiquement déchargés non conforme.
Roger Sanders
2
@Motti Je ne pense pas que la norme autorise explicitement les bibliothèques dynamiques, mais jusqu'à présent, je ne pensais pas non plus qu'il y ait quelque chose de spécifique dans la norme qui soit en contradiction avec sa mise en œuvre. Bien sûr, à proprement parler, le langage ici ne dit pas que les objets statiques ne peuvent pas être détruits plus tôt par d'autres moyens, juste qu'ils doivent être détruits lors du retour de main ou de l'appel à std :: exit. Une ligne assez fine bien que je pense.
Roger Sanders
125

Motti a raison sur la commande, mais il y a d'autres choses à considérer:

Les compilateurs utilisent généralement une variable d'indicateur masqué pour indiquer si la statique locale a déjà été initialisée, et cet indicateur est vérifié à chaque entrée de la fonction. Évidemment, il s'agit d'un petit succès de performances, mais ce qui est plus préoccupant, c'est que cet indicateur n'est pas garanti pour être thread-safe.

Si vous avez une statique locale comme ci-dessus et que foovous êtes appelé à partir de plusieurs threads, vous pouvez avoir des conditions de concurrence provoquant plonkune initialisation incorrecte ou même plusieurs fois. De plus, dans ce cas, il plonkpeut être détruit par un thread différent de celui qui l'a construit.

Malgré ce que dit la norme, je me méfierais de l'ordre réel de la destruction statique locale, car il est possible que vous vous appuyiez involontairement sur une statique toujours valide après sa destruction, et c'est vraiment difficile à retrouver.

Roddy
la source
68
C ++ 0x nécessite que l'initialisation statique soit sécurisée pour les threads. Alors méfiez-vous, mais les choses ne feront que s'améliorer.
deft_code
Les problèmes d'ordre de destruction peuvent être évités avec un peu de politique. les objets statiques / globaux (singletons, etc.) ne doivent pas accéder à d'autres objets statiques dans leurs corps de méthode. Ils ne seront accessibles que dans les constructeurs où une référence / pointeur peut être stocké pour un accès ultérieur dans les méthodes. Ce n'est pas parfait, mais devrait corriger 99 des cas et les cas qu'il ne capture pas sont évidemment louche et devraient être capturés dans une revue de code. Ce n'est toujours pas une solution parfaite car la politique ne peut pas être appliquée dans la langue
deft_code
Je suis un peu un noob, mais pourquoi cette politique ne peut-elle pas être appliquée dans la langue?
cjcurrie
9
Depuis C ++ 11, ce n'est plus un problème. La réponse de Motti est mise à jour en fonction de cela.
Nilanjan Basu
10

Les explications existantes ne sont pas vraiment complètes sans la règle réelle de la norme, trouvée en 6.7:

L'initialisation à zéro de toutes les variables de portée de bloc avec une durée de stockage statique ou une durée de stockage de thread est effectuée avant toute autre initialisation. L'initialisation constante d'une entité de portée de bloc avec une durée de stockage statique, le cas échéant, est effectuée avant la première entrée de son bloc. Une implémentation est autorisée à effectuer une initialisation précoce d'autres variables de portée de bloc avec une durée de stockage statique ou de thread dans les mêmes conditions qu'une implémentation est autorisée à initialiser statiquement une variable avec une durée de stockage statique ou de thread dans la portée de l'espace de noms. Sinon, une telle variable est initialisée la première fois que le contrôle passe par sa déclaration; une telle variable est considérée comme initialisée à l'issue de son initialisation. Si l'initialisation se termine en lançant une exception, l'initialisation n'est pas terminée, elle sera donc réessayée la prochaine fois que le contrôle entrera dans la déclaration. Si le contrôle entre simultanément dans la déclaration pendant l'initialisation de la variable, l'exécution simultanée attendra la fin de l'initialisation. Si le contrôle entre de nouveau récursivement dans la déclaration pendant l'initialisation de la variable, le comportement n'est pas défini.

Ben Voigt
la source
8

FWIW, Codegear C ++ Builder ne détruit pas dans l'ordre attendu selon la norme.

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

... ce qui est une autre raison de ne pas se fier à l'ordre de destruction!

Roddy
la source
57
Pas un bon argument. Je dirais que c'est plus un argument pour ne pas utiliser ce compilateur.
Martin York
26
Hmm. Si vous êtes intéressé à produire du code portable du monde réel, plutôt que simplement du code théoriquement portable, je pense qu'il est utile de savoir quelles zones du langage peuvent causer des problèmes. Je serais surpris si C ++ Builder était unique pour ne pas gérer cela.
Roddy
17
Je suis d'accord, sauf que je le formule comme «quels compilateurs causent des problèmes, et dans quels domaines du langage ils le font» ;-P
Steve Jessop
0

le variables statiques entrent en jeu une fois l' exécution du programme commencée et restent disponibles jusqu'à la fin de l'exécution du programme.

Les variables statiques sont créées dans le segment de données de la mémoire .

Chandra Shekhar
la source
ce n'est pas vrai pour les variables à la portée de la fonction
awerries