Quand les variables statiques au niveau de la fonction sont-elles allouées / initialisées?

89

Je suis convaincu que les variables déclarées globalement sont allouées (et initialisées, le cas échéant) au démarrage du programme.

int globalgarbage;
unsigned int anumber = 42;

Mais qu'en est-il des statiques définis dans une fonction?

void doSomething()
{
  static bool globalish = true;
  // ...
}

Quand l'espace est-il globalishalloué? Je devine quand le programme démarre. Mais est-il alors initialisé? Ou est-il initialisé lors de doSomething()son premier appel?

Owen
la source

Réponses:

91

J'étais curieux à ce sujet, j'ai donc écrit le programme de test suivant et l'ai compilé avec g ++ version 4.1.2.

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

Les résultats n'étaient pas ce à quoi je m'attendais. Le constructeur de l'objet statique n'a été appelé que lors du premier appel de la fonction. Voici la sortie:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
Adam Pierce
la source
29
Pour clarifier: la variable statique est initialisée la première fois que l'exécution atteint sa déclaration, pas lorsque la fonction contenant est appelée. Si vous avez juste un statique au début de la fonction (par exemple dans votre exemple), ce sont les mêmes, mais pas nécessairement: par exemple si vous avez 'if (...) {static MyClass x; ...} ', alors' x 'ne sera pas initialisé à ALL lors de la première exécution de cette fonction dans le cas où la condition de l'instruction if est évaluée à false.
EvanED
4
Mais cela n'entraîne-t-il pas une surcharge d'exécution, puisque chaque fois que la variable statique est utilisée, le programme doit vérifier si elle a déjà été utilisée, car sinon, il doit être initialisé? Dans ce cas, ça craint un peu.
HelloGoodbye
perfect illustration
Des1gnWizard
@veio: Oui, l'initialisation est thread-safe. Voir cette question pour plus de détails: stackoverflow.com/questions/23829389/…
Rémi
2
@HelloGoodbye: oui, cela entraîne une surcharge d'exécution. Voir aussi cette question: stackoverflow.com/questions/23829389/…
Rémi
53

Quelques verbiage pertinents de C ++ Standard:

3.6.2 Initialisation d'objets non locaux [basic.start.init]

1

Le stockage pour les objets avec une durée de stockage statique ( basic.stc.static ) doit être initialisé à zéro ( dcl.init ) avant toute autre initialisation. Les objets de types POD ( basic.types ) avec une durée de stockage statique initialisée avec des expressions constantes ( expr.const ) doivent être initialisés avant toute initialisation dynamique. Les objets de la portée de l'espace de noms avec une durée de stockage statique définie dans la même unité de traduction et initialisés dynamiquement doivent être initialisés dans l'ordre dans lequel leur définition apparaît dans l'unité de traduction. [Remarque: dcl.init.aggr décrit l'ordre dans lequel les membres de l'agrégat sont initialisés. L'initialisation des objets statiques locaux est décrite dans stmt.dcl . ]

[plus de texte ci-dessous ajoutant plus de libertés pour les auteurs de compilateurs]

6.7 Déclaration de déclaration [stmt.dcl]

...

4

L'initialisation à zéro ( dcl.init ) de tous les objets locaux avec une durée de stockage statique ( basic.stc.static ) est effectuée avant toute autre initialisation. Un objet local de type POD ( basic.types ) avec une durée de stockage statique initialisée avec des expressions constantes est initialisé avant la première entrée de son bloc. Une implémentation est autorisée à effectuer une initialisation précoce d'autres objets locaux avec une durée de stockage statique dans les mêmes conditions qu'une implémentation est autorisée à initialiser statiquement un objet avec une durée de stockage statique dans la portée de l'espace de noms ( basic.start.init). Sinon, un tel objet est initialisé la première fois que le contrôle de temps passe par sa déclaration; un tel objet est considéré comme initialisé à la fin de son initialisation. Si l'initialisation se termine en lançant une exception, l'initialisation n'est pas terminée, elle sera donc tentée à nouveau la prochaine fois que le contrôle entre dans la déclaration. Si le contrôle rentre la déclaration (récursivement) pendant l'initialisation de l'objet, le comportement n'est pas défini. [ Exemple:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- fin d'exemple ]

5

Le destructeur d'un objet local avec une durée de stockage statique sera exécuté si et seulement si la variable a été construite. [Remarque: basic.start.term décrit l'ordre dans lequel les objets locaux avec une durée de stockage statique sont détruits. ]

Jason Plank
la source
Cela a répondu à ma question et ne repose pas sur des «preuves anecdotiques» contrairement à la réponse acceptée. Je cherchais spécifiquement cette mention d'exceptions dans le constructeur d'objets statiques locaux de fonction initialisés statiquement:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge
26

La mémoire de toutes les variables statiques est allouée au chargement du programme. Mais les variables statiques locales sont créées et initialisées la première fois qu'elles sont utilisées, pas au démarrage du programme. Il y a de bonnes lectures à ce sujet, et sur la statique en général, ici . En général, je pense que certains de ces problèmes dépendent de la mise en œuvre, en particulier si vous voulez savoir où en mémoire ces éléments seront situés.

Eugène
la source
2
pas tout à fait, les statiques locales sont allouées et initialisées à zéro "au chargement du programme" (entre guillemets, car ce n'est pas tout à fait correct non plus), puis réinitialisées la première fois que la fonction dans laquelle elles sont utilisées est entrée.
Mooing Duck le
On dirait que ce lien est maintenant rompu, 7 ans plus tard.
Steve
1
Oui, le lien s'est rompu. Voici une archive: web.archive.org/web/20100328062506/http
Eugene
10

Le compilateur allouera des variables statiques définies dans une fonction fooau chargement du programme, mais le compilateur ajoutera également des instructions supplémentaires (code machine) à votre fonction fooafin que la première fois qu'il soit appelé, ce code supplémentaire initialise la variable statique ( par exemple en invoquant le constructeur, le cas échéant).

@Adam: Cette injection de code dans les coulisses par le compilateur est la raison du résultat que vous avez vu.

Henk
la source
5

J'essaye de tester à nouveau le code d' Adam Pierce et j'ai ajouté deux autres cas: variable statique dans la classe et type POD. Mon compilateur est g ++ 4.8.1, sous Windows OS (MinGW-32). Le résultat est une variable statique dans la classe est traitée de la même manière avec la variable globale. Son constructeur sera appelé avant d'entrer la fonction principale.

  • Conclusion (pour g ++, environnement Windows):

    1. Variable globale et membre statique dans la classe : le constructeur est appelé avant d'entrer la fonction main (1) .
    2. Variable statique locale : le constructeur n'est appelé que lorsque l'exécution atteint sa déclaration à la première fois.
    3. Si la variable statique locale est de type POD , elle est également initialisée avant d'entrer la fonction principale (1) . Exemple de type POD: nombre int statique = 10;

(1) : L'état correct doit être: "avant qu'une fonction de la même unité de traduction ne soit appelée". Cependant, pour simple, comme dans l'exemple ci-dessous, alors c'est la fonction principale .

inclure <iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

résultat:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

Quelqu'un testé dans l'environnement Linux?

Thang Le
la source
3

Les variables statiques sont allouées à l'intérieur d'un segment de code - elles font partie de l'image exécutable et sont donc mappées dans déjà initialisées.

Les variables statiques dans la portée de la fonction sont traitées de la même manière, la portée est purement une construction au niveau du langage.

Pour cette raison, vous avez la garantie qu'une variable statique sera initialisée à 0 (sauf si vous spécifiez autre chose) plutôt qu'une valeur indéfinie.

Il existe d'autres facettes de l'initialisation dont vous pouvez tirer parti - par exemple, les segments partagés permettent à différentes instances de votre exécutable s'exécutant en même temps d'accéder aux mêmes variables statiques.

En C ++ (à portée globale), les objets statiques ont leurs constructeurs appelés dans le cadre du démarrage du programme, sous le contrôle de la bibliothèque d'exécution C. Sous Visual C ++, au moins l'ordre dans lequel les objets sont initialisés peut être contrôlé par le pragma init_seg .

Rob Walker
la source
4
Cette question concerne la statique à portée fonctionnelle. Au moins quand ils ont des constructeurs non triviaux, ils sont initialisés à la première entrée dans la fonction. Ou plus précisément, lorsque cette ligne est atteinte.
Adam Mitz
Vrai - mais la question parle de l'espace alloué à la variable et utilise des types de données simples. L'espace est toujours alloué dans le segment de code
Rob Walker
Je ne vois pas en quoi le segment de code par rapport au segment de données compte vraiment ici. Je pense que nous avons besoin d'éclaircissements de la part du PO. Il a dit "et initialisé, le cas échéant".
Adam Mitz du
5
les variables ne sont jamais allouées à l'intérieur du segment de code; de cette façon, ils ne pourraient pas être écrits.
botismarius le
1
Les variables statiques se voient allouer de l'espace dans le segment de données ou dans le segment bss selon qu'elles sont initialisées ou non.
EmptyData
3

Ou est-il initialisé lorsque doSomething () est appelé pour la première fois?

Oui, ça l'est. Ceci, entre autres, vous permet d'initialiser les structures de données accessibles globalement lorsque cela est approprié, par exemple à l'intérieur de blocs try / catch. Par exemple au lieu de

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

tu peux écrire

int& foo() {
  static int myfoo = init();
  return myfoo;
}

et utilisez-le dans le bloc try / catch. Au premier appel, la variable sera initialisée. Ensuite, au premier et au prochain appel, sa valeur sera retournée (par référence).

Dmityugov
la source