2009-05-22 28 views
19

Ciekawi mnie podstawowa implementacja zmiennych statycznych w funkcji.W jaki sposób inicjowana jest zmienna statyczna przez kompilator?

Jeśli zadeklaruję zmienną statyczną typu podstawowego (char, int, double itp.) I nadam jej wartość początkową, wyobrażam sobie, że kompilator po prostu ustawia wartość tej zmiennej na samym początku Program przed main() nazywa się:

void SomeFunction(); 

int main(int argCount, char ** argList) 
{ 
    // at this point, the memory reserved for 'answer' 
    // already contains the value of 42 
    SomeFunction(); 
} 

void SomeFunction() 
{ 
    static int answer = 42; 
} 

jednak jeśli zmienna statyczna jest instancją klasy:

class MyClass 
{ 
    //... 
}; 

void SomeFunction(); 

int main(int argCount, char ** argList) 
{ 
    SomeFunction(); 
} 

void SomeFunction() 
{ 
    static MyClass myVar; 
} 

wiem, że nie zostaną zainicjowane dopiero po raz pierwszy, że funkcja jest wywoływana . Ponieważ kompilator nie ma możliwości poznania, kiedy funkcja zostanie wywołana po raz pierwszy, w jaki sposób generuje to zachowanie? Czy zasadniczo wprowadza on blok funkcyjny do ciała funkcji?

static bool initialized = 0; 
if (!initialized) 
{ 
    // construct myVar 
    initialized = 1; 
} 

Odpowiedz

11

W wyjściu kompilatora widziałem, funkcja lokalne zmienne statyczne są inicjowane dokładnie tak, jak można sobie wyobrazić.

Należy pamiętać, że ogólnie jest to , a nie wykonane w sposób bezpieczny dla wątków. Więc jeśli masz funkcje ze statycznymi lokalnymi, takimi jak te, które mogą być wywoływane z wielu wątków, powinieneś wziąć to pod uwagę. Wywołanie funkcji raz w głównym wątku, zanim inne zostaną wywołane, zwykle wystarczy.

Powinienem dodać, że jeśli inicjalizacja lokalnego statycznego odbywa się prostą stałą, jak w twoim przykładzie, kompilator nie musi przechodzić przez te ruchy - może po prostu zainicjować zmienną w obrazie lub przed main() jak regularna inicjalizacja statyczna (ponieważ twój program nie byłby w stanie odróżnić). Ale jeśli zainicjalizujesz ją wartością zwracaną przez funkcję, to kompilator musi przetestować flagę wskazującą, czy inicjalizacja została wykonana, czy coś podobnego.

+0

Czy coś się zmieniło? Słyszałem, że po C++ 11 każda inicjalizacja statyczna jest wątkowo bezpieczna. –

+0

@VictorPolevoy: Tak - kiedy ta odpowiedź została napisana, C++ 11 nie istniało. W C++ 11 standard zawierał obsługę wątków, a to zostało dodane do opisu inicjalizacji zmiennych statycznych blokowych (6.7/4): "Jeśli formant wprowadza deklarację jednocześnie podczas inicjalizacji zmiennej, to współbieżne wykonanie musi czekać na zakończenie inicjalizacji ". –

+1

Proponuję edytować odpowiedź, ponieważ jest ona akceptowana jako odpowiedź i ma 10 głosów. Również to pytanie jest najbardziej widoczne na temat inicjalizacji zmiennych statycznych. –

2

Masz rację co do wszystkiego, łącznie z inicjalizowaną flagą jako wspólną implementacją. Zasadniczo jest to powód, dla którego inicjalizacja lokalnych lokalizacji nie jest bezpieczna dla wątków i dlaczego istnieje pthread_once.

Jedno lekkie zastrzeżenie: kompilator musi emitować kod, który "zachowuje się tak, jakby" statyczna zmienna lokalna została skonstruowana przy pierwszym użyciu. Ponieważ inicjalizacja całkowita nie ma skutków ubocznych (i nie wywołuje kodu użytkownika), to zależy od kompilatora podczas inicjowania int. Kod użytkownika nie może "legalnie" dowiedzieć się, co robi.

Oczywiście można spojrzeć na kod zespołu lub prowokować niezdefiniowane zachowanie i dokonać dedukcji z tego, co faktycznie się dzieje. Ale standard C++ nie liczy się z tym, że jako uzasadnione powody twierdzą, że zachowanie nie jest "tak, jakby" zrobiło to, co mówi spec.

1

Wiem, że nie zostanie zainicjowany do czasu pierwszego wywołania tej funkcji. Ponieważ kompilator nie ma możliwości poznania, kiedy funkcja zostanie wywołana po raz pierwszy, w jaki sposób generuje to zachowanie? Czy zasadniczo wprowadza on blok funkcyjny do ciała funkcji?

Tak, to prawda: i, FWIW, niekoniecznie jest bezpieczny dla wątków (jeśli funkcja jest nazywana "po raz pierwszy" przez dwa wątki jednocześnie).

Z tego powodu wolisz zdefiniować zmienną w zasięgu globalnym (chociaż może w klasie lub przestrzeni nazw lub statycznie bez powiązania zewnętrznego) zamiast wewnątrz funkcji, tak aby została zainicjowana przed uruchomieniem programu bez żadnego uruchomienia. czas "jeśli".

10

This question pokrywał podobne podłoże, ale bezpieczeństwo nici nie było wspomniane. C++, co jest warte, sprawi, że wątek inicjalizacji funkcji będzie bezpieczny.

(patrz C++0x FCD, 6,7/4 na statyki funkcyjnych „Jeśli kontrola wchodzi deklarację równocześnie zaś zmienna jest zainicjowany, równoczesne wykonanie musi czekać na zakończeniu inicjalizacji”)

Innym rzeczą, o której nie wspomniano, jest to, że statyka funkcjonalna jest niszczona w odwrotnej kolejności od ich konstrukcji, więc kompilator utrzymuje listę destruktorów, z którymi można się połączyć przy wyłączaniu (ta lub inna może być lista, która jest używana przez Atexit).

+2

Czy możesz podać odniesienie/cytat do tego, że jest bezpieczny dla wątków w C++ 0x? Nie znalazłem. – ChrisW

1

Kolejny zwrot jest w kodzie osadzonym, gdzie kod run-before-main() (cinit/cokolwiek) może kopiować wstępnie zainicjalizowane dane (zarówno statyczne, jak i nie-statyczne) do ramka z segmentu danych stałych, być może rezydujące w ROM. Jest to przydatne, gdy kod może nie być uruchomiony z jakiegoś magazynu kopii zapasowej (dysku), z którego można go ponownie załadować. Ponownie, nie narusza to wymagań języka, ponieważ odbywa się to przed main().

Nieznaczna styczność: chociaż nie widziałem, aby to zrobiło dużo (poza Emacsem), program lub kompilator mógłby zasadniczo uruchomić twój kod w procesie i utworzyć instancje/zainicjować obiekty, a następnie zawiesić i zrzucić proces. Emacs robi coś podobnego do tego, aby załadować duże ilości elipsów (to znaczy żuć na nim), a następnie zrzucić stan roboczy jako działający plik wykonywalny, aby uniknąć kosztu analizowania przy każdym wywołaniu.

Powiązane problemy