Jaki jest czas życia zmiennej statycznej w funkcji C ++?

373

Jeśli zmienna jest zadeklarowana jak staticw zakresie funkcji, jest inicjowana tylko raz i zachowuje swoją wartość między wywołaniami funkcji. Jaka jest dokładnie jego żywotność? Kiedy wywoływany jest jego konstruktor i destruktor?

void foo() 
{ 
    static string plonk = "When will I die?";
}
Motti
źródło

Odpowiedzi:

257

Żywotność staticzmiennych funkcyjnych rozpoczyna się po raz pierwszy [0], gdy przepływ programu napotyka deklarację i kończy się wraz z zakończeniem programu. Oznacza to, że środowisko wykonawcze musi wykonać księgowość, aby zniszczyć ją tylko wtedy, gdy została faktycznie zbudowana.

Dodatkowo, ponieważ norma mówi, że niszczyciele obiektów statycznych muszą działać w odwrotnej kolejności do zakończenia ich budowy [1] , a kolejność budowy może zależeć od konkretnego uruchomienia programu, należy wziąć pod uwagę kolejność budowy .

Przykład

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

Wynik:

C:> sample.exe
Utworzono w foo
Zniszczono w foo

C:> sample.exe 1
Utworzono w, jeśli
Utworzono w foo
Zniszczono w foo
Zniszczono w, jeśli

C:> sample.exe 1 2
Utworzono w foo
Utworzono w przypadku
zniszczenia w przypadku
zniszczenia w foo

[0]Ponieważ C ++ 98 [2] nie ma odniesienia do wielu wątków, jak to będzie się zachowywać w środowisku wielowątkowym, jest nieokreślone i może być problematyczne, jak wspomina Roddy .

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

[2]W C ++ 11 statyka jest inicjowana w sposób bezpieczny dla wątków, jest to również znane jako Magic Statics .

Motti
źródło
2
W przypadku prostych typów bez efektów ubocznych c'tor / d'tor jest to prosta optymalizacja, aby zainicjować je w taki sam sposób, jak globalne typy proste. Pozwala to uniknąć problemów z rozgałęzianiem, flagą i porządkiem zniszczenia. Nie oznacza to, że ich życie jest inne.
John McFarlane
1
Jeśli funkcja może być wywoływana przez wiele wątków, oznacza to, że musisz upewnić się, że deklaracje statyczne muszą być chronione przez muteks w C ++ 98 ??
allyourcode
1
„niszczyciele” obiektów globalnych muszą działać w odwrotnej kolejności od zakończenia ich budowy ”nie ma tutaj zastosowania, ponieważ obiekty te nie są globalne. Kolejność niszczenia miejscowych ze statycznym lub czasowym przechowywaniem nici jest znacznie bardziej skomplikowana niż czysty LIFO, patrz sekcja 3.6.3[basic.start.term]
Ben Voigt
2
Wyrażenie „w momencie zakończenia programu” nie jest ściśle poprawne. Co ze statyką w bibliotekach DLL systemu Windows ładowanych i rozładowywanych dynamicznie? Oczywiście standard C ++ wcale nie zajmuje się zestawami (byłoby miło, gdyby tak się stało), ale wyjaśnienie dokładnie tego, co mówi tutaj standard, byłoby dobre. Gdyby uwzględniono wyrażenie „przy zakończeniu programu”, technicznie sprawiłoby to, że jakakolwiek implementacja C ++ z dynamicznie rozładowanymi zestawami byłaby niezgodna.
Roger Sanders
2
@Motti Nie wierzę, że standard wyraźnie zezwala na biblioteki dynamiczne, ale do tej pory nie wierzyłem również, że w tym standardzie było coś, co kolidowało z jego implementacją. Oczywiście, mówiąc ściśle w tym języku, nie stwierdza się, że obiektów statycznych nie można wcześniej zniszczyć innymi środkami, a jedynie, że muszą zostać zniszczone podczas powrotu z głównego lub wywołania std :: exit. Myślę, że całkiem niezła linia.
Roger Sanders
125

Motti ma rację co do zamówienia, ale jest kilka innych rzeczy do rozważenia:

Kompilatory zwykle używają ukrytej zmiennej flagi, aby wskazać, czy lokalna statystyka została już zainicjowana, a ta flaga jest sprawdzana przy każdym wejściu do funkcji. Oczywiście jest to niewielki hit wydajnościowy, ale bardziej niepokojące jest to, że ta flaga nie jest gwarantowana pod kątem bezpieczeństwa wątków.

Jeśli masz lokalny statyczny jak wyżej i foojest wywoływany z wielu wątków, możesz mieć warunki wyścigu powodujące, plonkże będzie on inicjowany niepoprawnie lub nawet wiele razy. Również w tym przypadku plonkmoże zostać zniszczony przez inny wątek niż ten, który go skonstruował.

Pomimo tego, co mówi standard, byłbym bardzo ostrożny z faktyczną kolejnością lokalnego niszczenia statycznego, ponieważ możliwe jest, że możesz nieświadomie polegać na istnieniu ładunków statycznych po zniszczeniu, a to jest naprawdę trudne do wyśledzenia.

Roddy
źródło
68
C ++ 0x wymaga, aby inicjalizacja statyczna była bezpieczna dla wątków. Bądź więc ostrożny, ale wszystko się poprawi.
deft_code,
Problemów związanych z porządkiem zniszczenia można uniknąć dzięki małej polityce. obiekty statyczne / globalne (singletony itp.) nie mają dostępu do innych obiektów statycznych w swoich ciałach metod. Są one dostępne tylko w konstruktorach, w których można zapisać odniesienie / wskaźnik w celu późniejszego dostępu metodami. Nie jest to idealne, ale powinno naprawić 99 przypadków, a przypadki, których nie łapie, są oczywiście podejrzane i powinny zostać złapane podczas przeglądu kodu. To wciąż nie jest idealna poprawka, ponieważ zasady nie można egzekwować w języku
deft_code,
Jestem trochę noobem, ale dlaczego nie można egzekwować tej polityki w tym języku?
cjcurrie
9
Od wersji C ++ 11 nie jest to już problemem. Odpowiedź Mottiego jest aktualizowana zgodnie z tym.
Nilanjan Basu
10

Istniejące wyjaśnienia nie są tak naprawdę kompletne bez rzeczywistej reguły ze Standardu, znalezionej w 6.7:

Inicjalizacja zera wszystkich zmiennych o zasięgu blokowym ze statycznym czasem przechowywania lub czasem przechowywania wątku jest wykonywana przed jakąkolwiek inną inicjalizacją. Stała inicjalizacja jednostki o zasięgu blokowym ze statycznym czasem przechowywania, jeśli ma to zastosowanie, jest wykonywana przed pierwszym wprowadzeniem jej bloku. Implementacja może wykonywać wczesną inicjalizację innych zmiennych o zasięgu blokowym ze statycznym lub czasowym przechowywaniem wątków w tych samych warunkach, na których implementacja może statycznie inicjować zmienną o statycznym lub czasowym przechowywaniu wątku w zakresie przestrzeni nazw. W przeciwnym razie taka zmienna jest inicjowana, gdy kontrola po raz pierwszy przechodzi przez deklarację; taka zmienna jest uważana za zainicjowaną po zakończeniu jej inicjalizacji. Jeśli inicjalizacja zakończy się przez zgłoszenie wyjątku, inicjalizacja nie jest zakończona, więc zostanie podjęta ponownie, gdy formant wejdzie do deklaracji. Jeżeli kontrola wprowadzi deklarację jednocześnie podczas inicjalizacji zmiennej, równoczesne wykonanie będzie czekać na zakończenie inicjalizacji. Jeśli formant ponownie wprowadzi deklarację rekurencyjnie podczas inicjalizacji zmiennej, zachowanie jest niezdefiniowane.

Ben Voigt
źródło
8

FWIW, Codegear C ++ Builder nie niszczy w oczekiwanej kolejności zgodnie ze standardem.

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

... co jest kolejnym powodem, aby nie polegać na nakazie zniszczenia!

Roddy
źródło
57
Niezbyt dobry argument. Powiedziałbym, że to raczej argument, aby nie używać tego kompilatora.
Martin York,
26
Hmm Jeśli interesuje Cię tworzenie przenośnego kodu w świecie rzeczywistym, a nie tylko teoretycznie przenośnego kodu, myślę, że warto wiedzieć, które obszary języka mogą powodować problemy. Byłbym zaskoczony, gdyby C ++ Builder był wyjątkowy w nieobsługiwaniu tego.
Roddy
17
Zgadzam się, z wyjątkiem tego, że sformułuję to jako „w jakich kompilatorach powodują problemy i w jakich obszarach języka to robią” ;-P
Steve Jessop
0

Te zmienne statyczne są wchodzić w grę kiedyś rozpoczęciu wykonywania programu i pozostają dostępne aż po krańce wykonywania programu.

Zmienne statyczne są tworzone w segmencie danych pamięci .

Chandra Shekhar
źródło
nie dotyczy to zmiennych w zakresie funkcji
awersja