Was ist die Lebensdauer einer statischen Variablen in einer C ++ - Funktion?

373

Wenn eine Variable als staticim Funktionsumfang einer Funktion deklariert ist, wird sie nur einmal initialisiert und behält ihren Wert zwischen Funktionsaufrufen bei. Was genau ist seine Lebensdauer? Wann werden sein Konstruktor und Destruktor aufgerufen?

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

Antworten:

257

Die Lebensdauer von Funktionsvariablen staticbeginnt beim ersten Auftreten [0] des Programmflusses auf die Deklaration und endet beim Beenden des Programms. Dies bedeutet, dass die Laufzeit eine Buchführung durchführen muss, um sie nur dann zu zerstören, wenn sie tatsächlich erstellt wurde.

Da der Standard besagt, dass die Destruktoren statischer Objekte in umgekehrter Reihenfolge nach Abschluss ihrer Konstruktion ausgeführt werden müssen [1] und die Reihenfolge der Konstruktion von der spezifischen Programmausführung abhängen kann, muss außerdem die Reihenfolge der Konstruktion berücksichtigt werden .

Beispiel

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

Ausgabe:

C:> sample.exe
Erstellt in foo
Zerstört in foo

C:> sample.exe 1
Erstellt in if
Erstellt in foo
Zerstört in foo
Zerstört in if

C:> sample.exe 1 2
Erstellt in foo
Erstellt in wenn
zerstört in wenn
zerstört in foo

[0]Da C ++ 98 [2] keinen Verweis auf mehrere Threads hat, ist das Verhalten in einer Umgebung mit mehreren Threads nicht spezifiziert und kann problematisch sein, wie Roddy erwähnt.

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

[2]In C ++ 11 werden Statiken threadsicher initialisiert. Dies wird auch als Magic Statics bezeichnet .

Motti
quelle
2
Für einfache Typen ohne c'tor / d'tor-Nebenwirkungen ist es eine einfache Optimierung, sie auf die gleiche Weise wie globale einfache Typen zu initialisieren. Dies vermeidet die Verzweigung, die Flagge und die Reihenfolge der Zerstörung. Das heißt nicht, dass ihr Leben anders ist.
John McFarlane
1
Wenn die Funktion von mehreren Threads aufgerufen werden kann, bedeutet dies, dass Sie sicherstellen müssen, dass statische Deklarationen in C ++ 98 durch einen Mutex geschützt werden müssen?
Allyourcode
1
"Destruktoren globaler Objekte müssen in umgekehrter Reihenfolge nach Abschluss ihrer Konstruktion ausgeführt werden" gilt hier nicht, da diese Objekte nicht global sind. Die Zerstörungsreihenfolge von Einheimischen mit statischer oder Thread-Speicherdauer ist erheblich komplizierter als bei reinem LIFO, siehe Abschnitt 3.6.3[basic.start.term]
Ben Voigt
2
Der Ausdruck "bei Programmbeendigung" ist nicht genau richtig. Was ist mit der Statik in Windows-DLLs, die dynamisch geladen und entladen werden? Offensichtlich befasst sich der C ++ - Standard überhaupt nicht mit Assemblys (es wäre schön, wenn dies der Fall wäre), aber eine Klarstellung darüber, was der Standard hier genau sagt, wäre gut. Wenn der Ausdruck "bei Programmbeendigung" enthalten wäre, würde jede Implementierung von C ++ mit dynamisch entladenen Assemblys technisch nicht konform sein.
Roger Sanders
2
@Motti Ich glaube nicht, dass der Standard explizit dynamische Bibliotheken zulässt, aber bis jetzt habe ich auch nicht geglaubt, dass der Standard etwas enthält, das im Widerspruch zu seiner Implementierung steht. Streng genommen besagt die Sprache hier natürlich nicht, dass statische Objekte nicht früher auf andere Weise zerstört werden können, sondern dass sie zerstört werden müssen, wenn Sie von main zurückkehren oder std :: exit aufrufen. Eine ziemlich feine Linie, obwohl ich denke.
Roger Sanders
125

Motti hat Recht mit der Bestellung, aber es gibt noch einige andere Dinge zu beachten:

Compiler verwenden normalerweise eine versteckte Flag-Variable, um anzuzeigen, ob die lokale Statik bereits initialisiert wurde, und dieses Flag wird bei jedem Eintrag in die Funktion aktiviert. Natürlich ist dies ein kleiner Leistungseinbruch, aber was noch besorgniserregender ist, dass dieses Flag nicht garantiert threadsicher ist.

Wenn Sie eine lokale Statik wie oben haben und foovon mehreren Threads aufgerufen werden, können Race-Bedingungen auftreten, die dazu führen plonk, dass sie falsch oder sogar mehrmals initialisiert werden. Auch in diesem Fall plonkkann es durch einen anderen Thread als den, der ihn erstellt hat, zerstört werden.

Ungeachtet dessen, was der Standard sagt, wäre ich sehr vorsichtig mit der tatsächlichen Reihenfolge der lokalen statischen Zerstörung, da es möglich ist, dass Sie sich unabsichtlich darauf verlassen, dass eine Statik nach ihrer Zerstörung noch gültig ist, und dies ist wirklich schwer zu finden.

Roddy
quelle
68
Für C ++ 0x muss die statische Initialisierung threadsicher sein. Seien Sie also vorsichtig, aber die Dinge werden nur besser.
Deft_code
Probleme mit der Zerstörungsreihenfolge können mit ein wenig Politik vermieden werden. statische / globale Objekte (Singletons usw.) dürfen nicht auf andere statische Objekte in ihren Methodenkörpern zugreifen. Sie dürfen nur in Konstruktoren aufgerufen werden, in denen eine Referenz / ein Zeiger für den späteren Zugriff in Methoden gespeichert werden kann. Dies ist nicht perfekt, sollte aber 99 der Fälle beheben, und Fälle, die es nicht fängt, sind offensichtlich faul und sollten in einer Codeüberprüfung gefangen werden. Dies ist immer noch keine perfekte Lösung, da die Richtlinie nicht in der Sprache erzwungen werden kann
deft_code
Ich bin ein bisschen noob, aber warum kann diese Richtlinie nicht in der Sprache durchgesetzt werden?
cjcurrie
9
Seit C ++ 11 ist dies kein Problem mehr. Mottis Antwort wird entsprechend aktualisiert.
Nilanjan Basu
10

Die vorhandenen Erklärungen sind ohne die eigentliche Regel aus dem Standard in 6.7 nicht wirklich vollständig:

Die Nullinitialisierung aller Blockbereichsvariablen mit statischer Speicherdauer oder Threadspeicherdauer wird durchgeführt, bevor eine andere Initialisierung stattfindet. Die konstante Initialisierung einer Blockbereichsentität mit statischer Speicherdauer wird gegebenenfalls durchgeführt, bevor der Block zum ersten Mal eingegeben wird. Eine Implementierung kann eine frühe Initialisierung anderer Blockbereichsvariablen mit statischer oder Thread-Speicherdauer unter denselben Bedingungen durchführen, unter denen eine Implementierung eine Variable mit statischer oder Thread-Speicherdauer im Namespace-Bereich statisch initialisieren darf. Andernfalls wird eine solche Variable initialisiert, wenn die Steuerung zum ersten Mal ihre Deklaration durchläuft. Eine solche Variable gilt nach Abschluss ihrer Initialisierung als initialisiert. Wenn die Initialisierung durch Auslösen einer Ausnahme beendet wird, Die Initialisierung ist nicht abgeschlossen, daher wird sie beim nächsten Eingeben der Steuerung in die Deklaration erneut versucht. Wenn die Steuerung gleichzeitig in die Deklaration eingeht, während die Variable initialisiert wird, wartet die gleichzeitige Ausführung auf den Abschluss der Initialisierung. Wenn die Steuerung die Deklaration rekursiv erneut eingibt, während die Variable initialisiert wird, ist das Verhalten undefiniert.

Ben Voigt
quelle
8

FWIW, Codegear C ++ Builder zerstört nicht in der erwarteten Reihenfolge gemäß dem Standard.

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

... was ein weiterer Grund ist, sich nicht auf den Zerstörungsbefehl zu verlassen!

Roddy
quelle
57
Kein gutes Argument. Ich würde sagen, dies ist eher ein Argument, diesen Compiler nicht zu verwenden.
Martin York
26
Hmm. Wenn Sie daran interessiert sind, realen tragbaren Code zu erstellen, anstatt nur theoretisch portablen Code, ist es meiner Meinung nach hilfreich zu wissen, welche Bereiche der Sprache Probleme verursachen können. Es würde mich wundern, wenn C ++ Builder einzigartig darin wäre, dies nicht zu handhaben.
Roddy
17
Ich würde zustimmen, außer dass ich es als "welche Compiler Probleme verursachen und in welchen Bereichen der Sprache sie es tun" formulieren würde ;-P
Steve Jessop
0

Die statischen Variablen werden nach Beginn der Programmausführung ins Spiel gebracht und bleiben verfügbar, bis die Programmausführung endet.

Die statischen Variablen werden im Datensegment des Speichers erstellt .

Chandra Shekhar
quelle
Dies gilt nicht für Variablen im Funktionsumfang
Antworten