Wann werden statische Variablen auf Funktionsebene zugewiesen / initialisiert?

88

Ich bin ziemlich sicher, dass global deklarierte Variablen beim Programmstart zugewiesen (und gegebenenfalls initialisiert) werden.

int globalgarbage;
unsigned int anumber = 42;

Aber was ist mit statischen, die innerhalb einer Funktion definiert sind?

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

Wann ist der Platz globalishzugeteilt? Ich vermute, wann das Programm startet. Aber wird es dann auch initialisiert? Oder wird es initialisiert, wenn doSomething()es zum ersten Mal aufgerufen wird?

Owen
quelle

Antworten:

90

Ich war neugierig und schrieb das folgende Testprogramm und kompilierte es mit 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;
}

Die Ergebnisse waren nicht das, was ich erwartet hatte. Der Konstruktor für das statische Objekt wurde erst beim ersten Aufruf der Funktion aufgerufen. Hier ist die Ausgabe:

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
quelle
28
Zur Verdeutlichung: Die statische Variable wird initialisiert, wenn die Ausführung zum ersten Mal auf ihre Deklaration trifft, nicht wenn die enthaltende Funktion aufgerufen wird. Wenn Sie zu Beginn der Funktion nur eine Statik haben (z. B. in Ihrem Beispiel), sind diese gleich, aber nicht unbedingt: zB wenn Sie 'if (...) {static MyClass x; ...} ', dann wird' x 'bei der ersten Ausführung dieser Funktion überhaupt nicht initialisiert, wenn die Bedingung der if-Anweisung als false ausgewertet wird.
Evaned
4
Führt dies jedoch nicht zu einem Laufzeitaufwand, da das Programm bei jeder Verwendung der statischen Variablen prüfen muss, ob sie zuvor verwendet wurde, und wenn nicht, muss sie initialisiert werden? In diesem Fall saugt diese Art ein bisschen.
HelloGoodbye
perfekte Illustration
Des1gnWizard
@veio: Ja, die Initialisierung ist threadsicher. Siehe diese Frage für weitere Details: stackoverflow.com/questions/23829389/…
Rémi
2
@HelloGoodbye: Ja, es führt zu einem Laufzeit-Overhead. Siehe auch diese Frage: stackoverflow.com/questions/23829389/…
Rémi
53

Einige relevante Wörter aus C ++ Standard:

3.6.2 Initialisierung nicht lokaler Objekte [basic.start.init]

1

Der Speicher für Objekte mit statischer Speicherdauer ( basic.stc.static ) muss vor jeder anderen Initialisierung auf Null initialisiert werden ( dcl.init ). Objekte von POD-Typen ( basic.types ) mit statischer Speicherdauer, die mit konstanten Ausdrücken ( expr.const ) initialisiert wurden, müssen initialisiert werden, bevor eine dynamische Initialisierung stattfindet. Objekte des Namespace-Bereichs mit statischer Speicherdauer, die in derselben Übersetzungseinheit definiert und dynamisch initialisiert sind, werden in der Reihenfolge initialisiert, in der ihre Definition in der Übersetzungseinheit angezeigt wird. [Hinweis: dcl.init.aggr beschreibt die Reihenfolge, in der aggregierte Mitglieder initialisiert werden. Die Initialisierung lokaler statischer Objekte wird in stmt.dcl beschrieben . ]]

[mehr Text unten, der mehr Freiheiten für Compiler-Autoren hinzufügt]

6.7 Deklarationserklärung [stmt.dcl]

...

4

Die Nullinitialisierung ( dcl.init ) aller lokalen Objekte mit statischer Speicherdauer ( basic.stc.static ) wird durchgeführt, bevor eine andere Initialisierung stattfindet. Ein lokales Objekt vom Typ POD ( basic.types ) mit statischer Speicherdauer, das mit konstanten Ausdrücken initialisiert wurde, wird initialisiert, bevor sein Block zum ersten Mal eingegeben wird. Eine Implementierung kann eine frühe Initialisierung anderer lokaler Objekte mit statischer Speicherdauer unter denselben Bedingungen durchführen, unter denen eine Implementierung ein Objekt mit statischer Speicherdauer im Namespace-Bereich ( basic.start.init) statisch initialisieren darf). Andernfalls wird ein solches Objekt initialisiert, wenn die Steuerung zum ersten Mal ihre Deklaration durchläuft. Ein solches Objekt gilt nach Abschluss seiner Initialisierung als initialisiert. Wenn die Initialisierung durch Auslösen einer Ausnahme beendet wird, ist die Initialisierung nicht abgeschlossen, sodass sie beim nächsten Eingeben der Steuerung in die Deklaration erneut versucht wird. Wenn die Steuerung die Deklaration (rekursiv) erneut eingibt, während das Objekt initialisiert wird, ist das Verhalten undefiniert. [ Beispiel:

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

- Beispiel beenden ]

5

Der Destruktor für ein lokales Objekt mit statischer Speicherdauer wird genau dann ausgeführt, wenn die Variable erstellt wurde. [Hinweis: basic.start.term beschreibt die Reihenfolge, in der lokale Objekte mit statischer Speicherdauer zerstört werden. ]]

Jason Plank
quelle
Dies beantwortete meine Frage und stützt sich im Gegensatz zur akzeptierten Antwort nicht auf "anekdotische Beweise". Ich habe speziell nach dieser Erwähnung von Ausnahmen im Konstruktor statisch initialisierter funktionaler lokaler statischer Objekte 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.
gesucht
26

Der Speicher für alle statischen Variablen wird beim Laden des Programms zugewiesen. Lokale statische Variablen werden jedoch bei der ersten Verwendung erstellt und initialisiert, nicht beim Programmstart. Es gibt einige gute Lektüre über das, und Statik in der Regel hier . Im Allgemeinen denke ich, dass einige dieser Probleme von der Implementierung abhängen, insbesondere wenn Sie wissen möchten, wo sich dieses Material im Speicher befindet.

Eugene
quelle
2
Nicht ganz, lokale Statiken werden "beim Laden des Programms" zugewiesen und auf Null initialisiert (in Anführungszeichen, da dies auch nicht ganz richtig ist) und dann bei der ersten Eingabe der Funktion, in der sie sich befinden, neu initialisiert.
Mooing Duck
Sieht so aus, als ob diese Verbindung jetzt, 7 Jahre später, unterbrochen ist.
Steve
1
Ja, die Verbindung ist unterbrochen. Hier ist ein Archiv: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene
10

Der Compiler weist statische Variablen zu, die in einer Funktion foobeim Laden des Programms definiert sind. Der Compiler fügt Ihrer Funktion jedoch auch einige zusätzliche Anweisungen (Maschinencode) hinzu, foosodass dieser zusätzliche Code beim ersten Aufruf die statische Variable initialisiert ( zB Aufrufen des Konstruktors, falls zutreffend).

@Adam: Diese Code-Injektion hinter den Kulissen durch den Compiler ist der Grund für das Ergebnis, das Sie gesehen haben.

Henk
quelle
5

Ich versuche erneut, Code von Adam Pierce zu testen, und füge zwei weitere Fälle hinzu: statische Variable in Klasse und POD-Typ. Mein Compiler ist g ++ 4.8.1 unter Windows (MinGW-32). Das Ergebnis ist eine statische Variable in der Klasse, die mit einer globalen Variablen gleich behandelt wird. Sein Konstruktor wird vor der Eingabe der Hauptfunktion aufgerufen.

  • Schlussfolgerung (für g ++, Windows-Umgebung):

    1. Globaler Variable und statisches Element in der Klasse : Konstruktor wird vor dem Teilnehmer Hauptfunktion (1) .
    2. Lokale statische Variable : Der Konstruktor wird nur aufgerufen, wenn die Ausführung zum ersten Mal ihre Deklaration erreicht.
    3. Wenn lokale statische Variable POD - Typ ist , dann wird es auch vor initialisiert eingeben Hauptfunktion (1) . Beispiel für POD-Typ: static int number = 10;

(1) : Der korrekte Zustand sollte lauten: "Bevor eine Funktion derselben Übersetzungseinheit aufgerufen wird". Für einfache, wie im folgenden Beispiel, ist es jedoch die Hauptfunktion.

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

Ergebnis:

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

Hat jemand in Linux env getestet?

Thang Le
quelle
3

Statische Variablen werden innerhalb eines Codesegments zugewiesen - sie sind Teil des ausführbaren Images und werden daher bereits initialisiert zugeordnet.

Statische Variablen innerhalb des Funktionsumfangs werden gleich behandelt, das Scoping ist lediglich ein Konstrukt auf Sprachebene.

Aus diesem Grund wird Ihnen garantiert, dass eine statische Variable auf 0 initialisiert wird (sofern Sie nichts anderes angeben) und nicht auf einen undefinierten Wert.

Die Initialisierung bietet noch einige andere Aspekte, die Sie nutzen können. Beispielsweise ermöglichen gemeinsam genutzte Segmente, dass verschiedene Instanzen Ihrer ausführbaren Datei gleichzeitig auf dieselben statischen Variablen zugreifen.

In C ++ (mit globalem Gültigkeitsbereich) werden statische Objekte als Teil des Programmstarts unter der Kontrolle der C-Laufzeitbibliothek aufgerufen. Unter Visual C ++ kann mindestens die Reihenfolge, in der Objekte initialisiert werden, durch das Pragma init_seg gesteuert werden .

Rob Walker
quelle
4
Bei dieser Frage geht es um funktionsbezogene Statik. Zumindest wenn sie nichttriviale Konstruktoren haben, werden sie beim ersten Eintritt in die Funktion initialisiert. Oder genauer gesagt, wenn diese Linie erreicht ist.
Adam Mitz
Richtig - aber die Frage bezieht sich auf den der Variablen zugewiesenen Speicherplatz und verwendet einfache Datentypen. Der Platz ist noch im Codesegment zugeordnet
Rob Walker
Ich sehe nicht, wie wichtig Codesegment gegen Datensegment hier ist. Ich denke, wir brauchen eine Klärung durch das OP. Er sagte "und initialisierte gegebenenfalls".
Adam Mitz
5
Variablen werden niemals innerhalb des Codesegments zugewiesen. auf diese Weise wären sie nicht schreibbar.
Botismarius
1
statischen Variablen wird Speicherplatz im Datensegment oder im BSS-Segment zugewiesen, je nachdem, ob sie initialisiert wurden oder nicht.
EmptyData
3

Oder wird es initialisiert, wenn doSomething () zum ersten Mal aufgerufen wird?

Ja, so ist es. Auf diese Weise können Sie unter anderem Datenstrukturen mit globalem Zugriff initialisieren, wenn dies angemessen ist, z. B. innerhalb von Try / Catch-Blöcken. ZB statt

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

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

Du kannst schreiben

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

und verwenden Sie es im try / catch-Block. Beim ersten Aufruf wird die Variable initialisiert. Beim ersten und nächsten Aufruf wird dann sein Wert zurückgegeben (als Referenz).

dmityugov
quelle