2008-10-29 18 views
289

Jeśli zmienna jest zadeklarowana jako static w zakresie funkcji, jest inicjowana tylko raz i zachowuje swoją wartość między wywołaniami funkcji. Czym dokładnie jest jego żywotność? Kiedy wywoływany jest jego konstruktor i destruktor?Jaki jest czas życia zmiennej statycznej w funkcji C++?

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

PS: Dla tych, którzy chcą wiedzieć why I asked the question if I already knew the answer?

+17

Normalnie nie głosuję na pytania, na które pytający natychmiast odpowiedział sam, ale ten jest interesujący. Dziękuję za to. –

+0

@Motti Link nie działa. – Zaimatsu

+0

@ Zaimatsu link nadal działa dla mnie. – Motti

Odpowiedz

177

The żywotność funkcji static zmienne zaczyna po raz pierwszy [0] przepływ program napotka deklarację i kończy się w momencie zakończenia programu. Oznacza to, że czas działania musi prowadzić pewne czynności księgowe, aby je zniszczyć tylko wtedy, gdy został faktycznie skonstruowany. Dodatkowo, ponieważ norma mówi, że destruktory obiektów statycznych muszą działać w kolejności odwrotnej do zakończenia ich budowy [1], a kolejność budowy może zależeć od konkretnego przebiegu programu, kolejności budowy Należy wziąć pod uwagę.

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

wyjściowa:

C:> Przykład.exe
Utworzony w foo
Zniszczony foo

C:> Przykład.exe 1
Utworzony jeśli
Utworzony w foo
zniszczone foo
zniszczone jeśli

C:> Przykład.exe 1 2
Utworzony w foo
Utworzony jeśli
zniszczone jeśli
zniszczone foo

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

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

[2] C++ 11 statyki inicjowane są w bezpieczny sposób, gwintów, to jest również znane jako Magic Statics.

+2

W przypadku prostych typów bez efektów ubocznych c'tor/d'tor prostą optymalizacją jest zainicjowanie ich w taki sam sposób, jak w przypadku prostych typów globalnych. Pozwala to uniknąć problemów związanych z rozgałęzianiem, flagą i porządkiem niszczenia. To nie znaczy, że ich życie jest inne. –

+0

Co z C++ 11? – allyourcode

+0

Jeśli funkcja może być wywołana przez wiele wątków, oznacza to, że musisz upewnić się, że deklaracje statyczne muszą być chronione przez muteks w C++ 98 ?? – allyourcode

114

Motti ma rację co do kolejności, ale są też inne rzeczy do rozważenia:

Kompilatory zazwyczaj użyć zmiennej flag ukryty, aby wskazać, czy lokalne statyka już zainicjowany, a ta flaga jest sprawdzany na każdym wejściu do funkcji. Oczywiście jest to mały hit wydajnościowy, ale bardziej niepokojące jest to, że ta flaga nie ma gwarancji, że jest bezpieczna dla wątków.

Jeśli masz lokalną statystykę jak powyżej, a "foo" jest wywoływane z wielu wątków, możesz mieć warunki wyścigu, które powodują, że 'plonk' zostanie zainicjalizowany niepoprawnie, a nawet wiele razy. Również w tym przypadku "plon" może zostać zniszczony przez inny wątek niż ten, który go skonstruował.

Pomimo tego, co standard mówi, byłbym bardzo ostrożny wobec rzeczywistej kolejności lokalnego statycznego zniszczenia, ponieważ możliwe jest, że możesz nieświadomie polegać na statycznej wciąż ważnej po jej zniszczeniu, a to jest naprawdę trudne wyśledzić.

+56

C++ 0x wymaga, aby inicjalizacja statyczna była bezpieczna dla wątków. Bądź ostrożny, ale sprawy będą jeszcze lepsze. –

+0

Problemy z porządkiem zniszczenia można uniknąć za pomocą niewielkiej polityki. obiekty statyczne/globalne (single, itp.) nie mają dostępu do innych obiektów statycznych w ich ciałach metod. Dostęp do nich jest możliwy tylko w przypadku konstruktorów, w których odniesienie/wskaźnik może być przechowywany w celu późniejszego dostępu do metod. Nie jest to doskonałe, ale powinno naprawić 99 przypadków, a przypadki, w których nie są wykrywane, są oczywiście podejrzane i powinny zostać uwzględnione w przeglądzie kodu. To wciąż nie jest doskonała poprawka, ponieważ polityka nie może być egzekwowana w języku –

+0

Jestem trochę noob, ale dlaczego ta polityka nie może być egzekwowana w języku? – cjcurrie

8

FWIW, Codegear C++ Builder nie ulega zniszczeniu 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, dla którego nie można polegać na nakazie zniszczenia!

+49

Niezbyt dobry argument. Powiedziałbym, że jest to bardziej argument, aby nie używać tego kompilatora. –

+22

Hmm. Jeśli interesuje Cię tworzenie przenośnego kodu w świecie rzeczywistym, a nie teoretycznie przenośnego kodu, myślę, że warto wiedzieć, jakie obszary języka mogą powodować problemy. Byłbym zaskoczony, gdyby C++ Builder był wyjątkowy, nie radząc sobie z tym. – Roddy

+13

Zgadzam się, poza tym, że nazwałbym to "czym kompilatory powodują problemy i jakie obszary języka robią to w" ;-P –

9

istniejącego wyjaśnienia nie są naprawdę kompletna bez rzeczywistego reguły od standardu, znalezionych w 6.7:

zero-inicjalizacji wszystkich zmiennych blok-scope ze statycznym czas trwania składowania lub magazynowania wątku jest wykonywane przed rozpoczęciem dowolnej innej inicjalizacji. Stała inicjalizacja jednostki zakresu blokowego z czasem trwania statycznego przechowywania, jeśli ma zastosowanie, jest wykonywana przed jej pierwszym wprowadzeniem. Implementacja jest dozwolona do przeprowadzenia wczesnej inicjalizacji innych zmiennych o zakresie bloków z czasem trwania pamięci statycznej lub wątku w tych samych warunkach, w których implementacja jest dozwolona do statycznego zainicjowania zmiennej z czasem statycznym lub wątku w obszarze przestrzeni nazw. W przeciwnym razie inicjalizowana jest taka zmienna, gdy kontrola po raz pierwszy przechodzi przez deklarację; taka zmienna jest uważana za zainicjalizowaną po zakończeniu jej inicjalizacji. Jeśli inicjowanie zakończy się przez wyrzucenie wyjątku, inicjalizacja nie jest kompletna, więc zostanie wypróbowana ponownie, gdy następna kontrola przejdzie do deklaracji. Jeżeli sterowanie wprowadza deklarację jednocześnie podczas inicjalizacji zmiennej, to wykonanie współbieżne powinno czekać na zakończenie inicjalizacji. Jeśli sterowanie ponownie wraca do deklaracji rekurencyjnie podczas inicjalizacji zmiennej, zachowanie jest niezdefiniowane.

Powiązane problemy