O inicjalizacji zmiennych globalnych

2010-04-28 19:15

Wśród dziwnych błędów wykonania, jakie mogą przytrafić się źle napisanym programom w C++, poczesne miejsce zajmuje przypadek, gdy obiekt zadeklarowany po prostu jako zmienna:

  1. Foo foo;

w rzeczywistości nie istnieje (jeszcze). Dokładniej mówiąc: pamięć na zmienną foo, jest jak najbardziej zaalokowana, ale konstruktor klasy Foo nie został jeszcze wywołany.
Czy taka sytuacja jest w ogóle możliwa? Odpowiedź brzmi: jak najbardziej, jeśli rzeczona zmienna jest globalna lub statyczna w klasie (static) i odwołujemy się do niej podczas konstrukcji innej takowej zmiennej.

Uważa się to za bardzo złą praktykę z prostego powodu: kolejność inicjalizacji zmiennych globalnych/statycznych umieszczonych w różnych jednostkach translacji (czyli plikach .cpp) jest niezdefiniowana. Ci, którzy znają trochę terminologię używaną w standardach C/C++ wiedzą, iż znaczy to tyle, że porządek tej inicjalizacji może się zmieniać w zależności od pory dnia, fazy księżyca i poziomu opadów w dorzeczu Amazonki – czyli w praktyce od platformy, wersji kompilatora czy nawet specyficznych jego ustawień (np. optymalizacji). Nie można więc na niej polegać, bo jeśli nawet “teraz u mnie działa”, to wcale nie oznacza, że za jakiś czas nadal będzie.
Niestety, napisanie kodu zależnego od porządku konstrukcji zmiennych globalnych jest prostsze niż się wydaje. Wystarczy chociażby wyobrazić sobie grę złożoną z kilku podsystemów (dźwięku, grafiki, fizyki, UI, itp.), z których każdy wystawia globalny obiekt będący jego interfejsem. Jeśli rozpoczęcie pracy któregoś z tych podsystemów wymaga innego, już gotowego do pracy (np. UI korzystające z części graficznej), to wówczas pojawią się opisywane wyżej zależności między inicjalizacją obiektów globalnych. A wtedy mamy klops :)

Z problemem, jak to zwykle bywa, możemy poradzić sobie dwojako. Można go obejść, stosując funkcję dostępową do obiektu i zmienną statyczną wewnątrz tej funkcji:

  1. GfxSystem& Gfx() { static Gfx gfx; return gfx; }

Mamy wówczas pewność, że zwróci ona zawsze odwołanie do już skonstruowanego obiektu. Jest to możliwe dzięki własnościom zmiennych statycznych w funkcjach, które sprawiają, że obiekt ten zostanie po prostu utworzony przy pierwszym użyciu (wywołaniu funkcji).
Nazywam ten sposób obejściem, bo w najlepszym razie jest on nieelegancki, a w najgorszym może powodować problemy na drugim końcu życia obiektów, czyli podczas ich destrukcji. Można rzecz rozwiązać lepiej i w sposób bardziej oczywisty, chociaż mniej “efektowny” niż automatyczna konstrukcja obiektów przy pierwszym użyciu.

Mam tu na myśli stare, dobre inicjalizowanie jawne na początku działania programu:

  1. g_Gfx = new GfxSystem();
  2. g_Sfx = new SfxSystem();
  3. g_UI = new UiSystem(g_Gfx); // tu *g_Gfx na pewno istnieje

i równie wyraźne zwalnianie wszystkiego na końcu. Może to wszystko być w samej funkcji main/WinMain, może być w wydzielonym do tego miejscu – te szczegóły nie są aż takie ważne. Grunt, że w ten sposób żadna nietypowa (i niezdefiniowana) sytuacja nie powinna się już nam przytrafić.

Be Sociable, Share!
Be Sociable, Share!
Tags: , , ,
Author: Xion, posted under Programming »


9 comments for post “O inicjalizacji zmiennych globalnych”.
  1. Kot:
    April 28th, 2010 o 19:38

    Nie bójmy się słowa singleton i singleton singletonem nazywajmy :).
    A jeżeli już to mamy to przy użyciu kilku sztuczek możemy sterować też kolejnością deinicjalizacji – “singleton with longevity”.
    A kwestia czy używać w ogóle singeltonów to już temat na długą dyskusję :).

  2. aljen:
    April 29th, 2010 o 12:46

    W gcc do inicjalizacji mozna uzyc atrybutow constructor i destructor, np http://pastebin.com/j7hBWfUN

  3. Netrix:
    April 29th, 2010 o 23:48

    A nie lepiej zamiast new, dać stare dobre funkcje create/release, które wywoła się w main ?

  4. Xion:
    April 30th, 2010 o 0:38

    Też można – new w tym ostatnim kodzie to przykład bardziej poglądowy niż rzeczywisty. Chodzi po prostu o jawną inicjalizację.

  5. Anonymous:
    April 30th, 2010 o 14:02

    Ja np. chętnie bym się dowiedział, jakie problemy przy destrukcji powoduje takie “tworzenie singletonów w momencie pierwszego zapotrzebowania”. Ja w praktycznych zastosowaniach nie mialem tego problemu – podsystemy sa zwalniane w kolejnosci odwrotnej do inicjalizacji i tyle.

    (oczywiście nie przez sposób ze staticiem wewnątrz funkcji, tylko zwykłe if (INSTANCE == NULL) INSTANCE = new … ()).

  6. Xion:
    April 30th, 2010 o 16:28

    (oczywiście nie przez sposób ze staticiem wewnątrz funkcji, tylko zwykłe if (INSTANCE == NULL) INSTANCE = new … ()).

    No to podstawowym problemem będzie właśnie to, że ten singleton trzeba będzie gdzieś ręcznie zwolnić – w końcu to ręcznie zaalokowana pamięć, dla której wypadałoby chociaż wywołać destruktor. Oczywiście jeśli on nic robi, to widocznego wycieku nie będzie (system to sobie posprząta), ale w przeciwnym wypadku…

  7. Kos:
    May 1st, 2010 o 14:55

    O, Xion, publikują Cię na develway.pl :))

  8. Tomek:
    May 1st, 2010 o 22:52

    A mnie się wydawało (nie pamiętam gdzie to przeczytałem i czy na pewno to przeczytałem), że inicjacja obiektów globalnych (wywołanie ich konstruktora) następuje z momentem pierwszego wywołania dowolnej funkcji lub metody zdefiniowanej w danym pliku cpp, w który obiekt globalny występuje. To oznaczałoby, że kolejność inicjalizacji zmiennych jest zdeterminowana przez tą właśnie zasadę.

  9. Netrix:
    May 3rd, 2010 o 1:44

    Tak jest na pewno w Javie.

Comments are disabled.
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.