ПрограммированиеC++ системный архитектор / Senior Software Engineer

Какой порядок инициализации глобальных и статических объектов в C++? Как можно гарантировать корректную инициализацию между разными translation units?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

В C++ глобальные и статические объекты инициализируются до входа в функцию main(). Однако стандарт не гарантирует порядок инициализации объектов, находящихся в разных единицах трансляции (файлах компиляции, translation units). Это может приводить к так называемой "static initialization order fiasco" — багу, когда один глобальный объект ссылается на другой, а тот ещё не был инициализирован.

Чтобы гарантировать корректную инициализацию и избежать проблемы, используют паттерн "construct on first use": объявляют функцию, возвращающую ссылку на статический локальный объект (он создаётся при первом вызове потоко-безопасно с C++11).

Пример кода (паттерн construct on first use):

// foo.h class Config { public: int value; }; Config& getConfig() { static Config config; config.value = 42; // инициализация гарантирована! return config; }

Вопрос с подвохом.

Если в двух разных .cpp-файлах глобальные объекты создают друг друга, что произойдёт?

Ответ: Результат неопределён, может произойти обращение к неинициализированному объекту. Решение — заменить прямую глобальную инициализацию на ленивую через статик-локальный объект внутри функции (см. выше).


История

-В CRM-системе два модуля объявили глобальные логгеры, которые при инициализации ссылались друг на друга. В разных сборках приложения порядок инициализации менялся, появлялись трудноуловимые баги: падения и логгирование в нулевой указатель.


История

-В графическом движке глобальный объект-контейнер для ресурсов ссылался на менеджер ресурсов, который сам был глобальным. На Linux и Windows компиляторы реализовывали порядок загрузки по-разному, получалась рассинхронизация и разные сбои.


История

-Разработчик ввёл глобальную инициализацию настроек, а позже коллега добавил ещё одну глобальную переменную, зависящую от настроек. Это работало локально и ломалось на CI, где происходила другая компоновка модулей.