Kiedy zmienne statyczne na poziomie funkcji są przydzielane / inicjowane?

89

Jestem pewien, że globalnie zadeklarowane zmienne są przydzielane (i inicjowane, jeśli ma to zastosowanie) w czasie uruchamiania programu.

int globalgarbage;
unsigned int anumber = 42;

Ale co ze statycznymi wartościami zdefiniowanymi w funkcji?

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

Kiedy jest miejsce na globalishprzydzielenie? Zgaduję, kiedy program się uruchomi. Ale czy wtedy też jest inicjalizowany? Czy jest inicjowany, gdy doSomething()jest wywoływany po raz pierwszy?

piekarnik
źródło

Odpowiedzi:

91

Zaciekawiło mnie to, więc napisałem następujący program testowy i skompilowałem go z g ++ w wersji 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;
}

Wyniki nie były takie, jakich się spodziewałem. Konstruktor obiektu statycznego został wywołany dopiero po pierwszym wywołaniu funkcji. Oto wynik:

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
źródło
30
Dla wyjaśnienia: zmienna statyczna jest inicjowana, gdy wykonanie po raz pierwszy trafia w jej deklarację, a nie gdy wywoływana jest funkcja zawierająca. Jeśli masz tylko statyczną na początku funkcji (np. W twoim przykładzie), są one takie same, ale niekoniecznie: np. Jeśli masz 'if (...) {static MyClass x; ...} ', a następnie' x 'nie zostanie zainicjowany w ALL podczas pierwszego wykonania tej funkcji w przypadku, gdy warunek instrukcji if ma wartość false.
EvanED,
4
Ale czy nie prowadzi to do narzutu czasu wykonania, ponieważ za każdym razem, gdy używana jest zmienna statyczna, program musi sprawdzić, czy była wcześniej używana, a jeśli nie, musi zostać zainicjowana? W takim razie trochę to do niczego.
HelloGoodbye
doskonała ilustracja
Des1gnWizard
@veio: Tak, inicjalizacja jest bezpieczna wątkowo. Zobacz to pytanie, aby uzyskać więcej informacji: stackoverflow.com/questions/23829389/…
Rémi
2
@HelloGoodbye: tak, prowadzi to do obciążenia środowiska wykonawczego. Zobacz także to pytanie: stackoverflow.com/questions/23829389/…
Rémi
53

Kilka odpowiednich słów ze standardu C ++:

3.6.2 Inicjalizacja obiektów nielokalnych [basic.start.init]

1

Pamięć dla obiektów ze statycznym czasem trwania ( basic.stc.static ) powinna być inicjalizowana przez zero ( dcl.init ) przed jakąkolwiek inną inicjalizacją. Obiekty typu POD ( basic.types ) ze statycznym czasem przechowywania inicjowanym za pomocą stałych wyrażeń ( expr.const ) powinny być inicjalizowane przed jakąkolwiek dynamiczną inicjalizacją. Obiekty o zasięgu przestrzeni nazw ze statycznym czasem przechowywania zdefiniowanym w tej samej jednostce tłumaczeniowej i dynamicznie inicjowane powinny być inicjowane w kolejności, w jakiej ich definicja pojawia się w jednostce tłumaczeniowej. [Uwaga: dcl.init.aggr opisuje kolejność, w jakiej agregowane elementy członkowskie są inicjowane. Inicjalizacja lokalnych obiektów statycznych jest opisana w stmt.dcl . ]

[więcej tekstu poniżej dodające więcej swobód dla autorów kompilatorów]

6.7 Oświadczenie o deklaracji [stmt.dcl]

...

4

Inicjalizacja zerowa ( dcl.init ) wszystkich obiektów lokalnych ze statycznym czasem trwania ( basic.stc.static ) jest wykonywana przed jakąkolwiek inną inicjalizacją. Lokalny obiekt typu POD ( basic.types ) ze statycznym czasem przechowywania zainicjowanym za pomocą wyrażeń stałych jest inicjowany przed pierwszym wprowadzeniem jego bloku. Implementacja może wykonywać wczesną inicjalizację innych obiektów lokalnych ze statycznym czasem trwania w tych samych warunkach, w jakich implementacja może statycznie inicjować obiekt ze statycznym czasem trwania w zakresie przestrzeni nazw ( basic.start.init). W przeciwnym razie taki obiekt jest inicjowany, gdy sterowanie przechodzi po raz pierwszy przez swoją deklarację; taki obiekt jest uważany za zainicjowany po zakończeniu jego inicjalizacji. Jeśli inicjalizacja kończy się przez zgłoszenie wyjątku, inicjalizacja nie jest zakończona, więc zostanie podjęta ponowna próba, gdy następnym razem formant przejdzie do deklaracji. Jeśli sterowanie ponownie wprowadzi deklarację (rekurencyjnie) podczas inicjalizacji obiektu, zachowanie jest niezdefiniowane. [ Przykład:

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

- przykład końca ]

5

Destruktor dla obiektu lokalnego ze statycznym czasem trwania zostanie wykonany wtedy i tylko wtedy, gdy zmienna została skonstruowana. [Uwaga: basic.start.term opisuje kolejność niszczenia lokalnych obiektów ze statycznym czasem trwania. ]

Jason Plank
źródło
To odpowiedziała na moje pytanie i nie opiera się na „anegdotycznych dowodach” w przeciwieństwie do zaakceptowanej odpowiedzi. W szczególności szukałem tej wzmianki o wyjątkach w konstruktorze lokalnych statycznych obiektów statycznie zainicjowanych funkcji: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.
Bensge
26

Pamięć dla wszystkich zmiennych statycznych jest przydzielana podczas ładowania programu. Jednak lokalne zmienne statyczne są tworzone i inicjowane przy pierwszym użyciu, a nie podczas uruchamiania programu. Jest kilka dobrych czytanie o tym, i statyki w ogóle tutaj . Ogólnie myślę, że niektóre z tych problemów zależą od implementacji, szczególnie jeśli chcesz wiedzieć, gdzie w pamięci będą znajdować się te rzeczy.

Eugene
źródło
2
niezupełnie, lokalne statystyki są przydzielane i inicjowane zerem „przy ładowaniu programu” (w cudzysłowach, ponieważ to też nie jest do końca w porządku), a następnie ponownie inicjowane przy pierwszym wprowadzeniu funkcji, w której się znajdują.
Mooing Duck
Wygląda na to, że ten link jest teraz uszkodzony, 7 lat później.
Steve
1
Tak, łącze się zepsuło. Oto archiwum: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene
10

Kompilator przydzieli statyczne zmienne zdefiniowane w funkcji foopodczas ładowania programu, jednak kompilator doda również dodatkowe instrukcje (kod maszynowy) do twojej funkcji foo, aby przy pierwszym wywołaniu ten dodatkowy kod zainicjował zmienną statyczną ( np. wywołanie konstruktora, jeśli dotyczy).

@Adam: To, za kulisami, wstrzyknięcie kodu przez kompilator jest powodem tego, co zobaczyłeś.

Henk
źródło
5

Próbuję ponownie przetestować kod Adama Pierce'a i dodałem jeszcze dwa przypadki: zmienną statyczną w klasie i typ POD. Mój kompilator to g ++ 4.8.1 w systemie operacyjnym Windows (MinGW-32). Wynik jest zmienna statyczna w klasie jest traktowana tak samo jak zmienna globalna. Jego konstruktor zostanie wywołany przed wejściem do funkcji main.

  • Wniosek (dla g ++, środowisko Windows):

    1. Zmienna globalna i statyczny element członkowski w klasie : konstruktor jest wywoływany przed wejściem do funkcji głównej (1) .
    2. Lokalna zmienna statyczna : konstruktor jest wywoływany tylko wtedy, gdy wykonanie osiągnie swoją deklarację po raz pierwszy.
    3. Jeśli lokalna zmienna statyczna jest typu POD , to jest również inicjowana przed wejściem do funkcji głównej (1) . Przykład dla typu POD: static int number = 10;

(1) : Prawidłowym stanem powinno być: „zanim zostanie wywołana jakakolwiek funkcja z tej samej jednostki tłumaczeniowej”. Jednak dla prostoty, jak w przykładzie poniżej, jest to funkcja główna .

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

wynik:

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

Czy ktoś testował w środowisku Linux env?

Thang Le
źródło
3

Zmienne statyczne są alokowane w segmencie kodu - są częścią obrazu wykonywalnego, a więc są mapowane w już zainicjowanym.

Zmienne statyczne w zakresie funkcji są traktowane tak samo, zakres jest konstrukcją wyłącznie na poziomie języka.

Z tego powodu masz gwarancję, że zmienna statyczna zostanie zainicjowana na 0 (chyba że określisz coś innego) zamiast niezdefiniowanej wartości.

Istnieje kilka innych aspektów inicjalizacji, z których możesz skorzystać - na przykład segmenty współdzielone pozwalają różnym instancjom wykonywanego pliku wykonywalnego jednocześnie na dostęp do tych samych zmiennych statycznych.

W C ++ (o zasięgu globalnym) obiekty statyczne mają swoje konstruktory wywoływane podczas uruchamiania programu, pod kontrolą biblioteki wykonawczej C. W języku Visual C ++ przynajmniej kolejność inicjowania obiektów może być kontrolowana przez pragmę init_seg .

Rob Walker
źródło
4
To pytanie dotyczy statyki w zakresie funkcji. Przynajmniej jeśli mają nietrywialne konstruktory, są inicjowane przy pierwszym wejściu do funkcji. A dokładniej, kiedy ta linia zostanie osiągnięta.
Adam Mitz,
To prawda - ale pytanie dotyczy przestrzeni przydzielonej zmiennej i wykorzystuje proste typy danych. Miejsce jest nadal przydzielone w segmencie kodu
Rob Walker,
Nie widzę, jakie znaczenie ma tutaj segment kodu w porównaniu z segmentem danych. Myślę, że potrzebujemy wyjaśnienia z PO. Powiedział „i zainicjował, jeśli dotyczy”.
Adam Mitz,
5
zmienne nigdy nie są alokowane wewnątrz segmentu kodu; w ten sposób nie mogliby pisać.
botismarius
1
zmienne statyczne mają przydzielone miejsce w segmencie danych lub w segmencie bss w zależności od tego, czy są zainicjowane, czy nie.
EmptyData
3

A może jest inicjowany podczas pierwszego wywołania metody doSomething ()?

Tak to jest. Pozwala to między innymi na inicjalizację struktur danych, do których dostęp jest globalny, kiedy jest to stosowne, na przykład wewnątrz bloków try / catch. Np. Zamiast

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

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

Możesz pisać

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

i użyj go wewnątrz bloku try / catch. Przy pierwszym wywołaniu zmienna zostanie zainicjowana. Następnie, przy pierwszym i następnym wywołaniu, jego wartość zostanie zwrócona (przez odniesienie).

dmityugov
źródło