programowanieArchitekt systemów C++ / Starszy inżynier oprogramowania

Jaki jest porządek inicjalizacji obiektów globalnych i statycznych w C++? Jak można zapewnić poprawną inicjalizację między różnymi jednostkami tłumaczenia?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W C++ obiekty globalne i statyczne są inicjalizowane przed wejściem do funkcji main(). Jednak standard nie gwarantuje porządku inicjalizacji obiektów, które znajdują się w różnych jednostkach tłumaczenia (plikach kompilacji, translation units). Może to prowadzić do tzw. "static initialization order fiasco" — błędu, w którym jeden obiekt globalny odnosi się do drugiego, który jeszcze nie został zainicjalizowany.

Aby zapewnić poprawną inicjalizację i uniknąć problemu, stosuje się wzorzec "construct on first use": deklaruje się funkcję, która zwraca odniesienie do statycznego lokalnego obiektu (jest tworzony przy pierwszym wywołaniu w sposób bezpieczny dla wątków od C++11).

Przykład kodu (wzorzec construct on first use):

// foo.h class Config { public: int value; }; Config& getConfig() { static Config config; config.value = 42; // inicjalizacja zagwarantowana! return config; }

Pytanie z zagadką.

Co się stanie, jeśli w dwóch różnych plikach .cpp obiekty globalne tworzą się nawzajem?

Odpowiedź: Wynik jest nieokreślony, może wystąpić odwołanie do nieinicjalizowanego obiektu. Rozwiązanie — zastąpienie bezpośredniej globalnej inicjalizacji leniwą przez statyczny lokalny obiekt wewnątrz funkcji (patrz wyżej).


Historia

-W systemie CRM dwa moduły zadeklarowały globalne logger, które przy inicjalizacji odnosiły się nawzajem. W różnych kompilacjach aplikacji porządek inicjalizacji się zmieniał, pojawiały się trudne do uchwycenia błędy: awarie i logowanie do wskaźnika zerowego.


Historia

-W silniku graficznym globalny obiekt-kontener dla zasobów odnosił się do menedżera zasobów, który sam był globalny. Na Linuxie i Windows kompilatory realizowały porządek wczytywania w różny sposób, co prowadziło do desynchronizacji i różnych awarii.


Historia

-Rozdeveloper wprowadził globalną inicjalizację ustawień, a później kolega dodał jeszcze jedną globalną zmienną, która zależała od ustawień. Działało to lokalnie, a psuło się w CI, gdzie następowała inna kompozycja modułów.