Порядок инициализации глобальных статических переменных из разных единиц трансляции не определен. По-английский это называется красивым термином static initialization order fiasco. Это означает, что если у вас в разных файлах определены глобальные статические переменные x и y, то лучше, чтобы они не зависели одна от другой, а то тут могут быть неприятные сюрпризы.
Это было скучная теория. А вот грустные примеры из практики. Пишем много-много кода с большим количеством нетривиальных глобальных статических переменных. Они как-то самопроизвольно начинают друг от друга зависеть. До поры, до времени компиляция идет таким путем, что они нормально инициализируются. Но в один прекрасный день, после добавления какого-то куска кода, они вдруг начинают инициализироваться в немного другом порядке. И это означает, что надо переосмыслить весь проект целиком. Прямо сейчас.
Updated 12.07.2011:
В комментариях посоветовали два способа продлить агонию, т.е. заставить компилятор инициализировать глобальные переменные в определенном порядке.
Для GCC это init_priority
Для VC++ #pragma init_seg
P.S. Для примеров из жизни я обычно не указываю когда и кем были допущены какие-то ошибки. Это информация для того, чтобы научиться, а не для того, чтобы поглумиться. Мы все ошибаемся.
Quaternions and spherical trigonometry
2 дня назад
22 коммент.:
Если не боитесь темной стороны силы, в gcc есть способ определить порядок (priority) инициализации. http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html
но минус сто в карму сразу.
Алёна работает в Miscrosoft. Я думаю, что они не используют gcc в своих проектах.
Про -100 согласен на все 100.
ого, это ж подводные грабли как они есть! примерно как в моей епархии все, что связано с альфа-каналами
Глобальные переменные в коде - признак низкого качества кода.
Исправить/зарефакторить - религия не позволяет?
И именно поэтому паттерн "singleton" получил такую популярность. ;)
Помимо синглтона, есть ещё паттерн «счётчик Шварца».
По моему единственный случай когда оправдано использование статических переменных, - это реализация thread local storage.
Остальное все должно начинаться со стека int main(); и неторопливо уходить в heap.
Чтобы этого не происходило, стараюсь использовать минимум глобальных переменных и держать их в одной единице трансляции, специально для этого отведенной. В частности, это естественным образом умеряет желание создавать глобальные переменные.
не только инициализироваться, но и удаляться в произвольном порядке при завершении программы - тоже может боком вылезти... мы наткнулись на такое на gcc 4.6
Есть довольно простой обходной путь, который не требует глобального рефакторинга: http://altdevblogaday.com/2011/07/07/writing-a-pre-main-function-forcing-global-initialization-order-within-vc/
А я вообще не понимаю, что можно писать, чтобы сесть в такую лужу?
Алена, а внедрение зависимостей не стали использовать?
Qehgt
Глобальные переменные в коде - признак низкого качества кода.
Исправить/зарефакторить - религия не позволяет?
Если проект большой, то его полный рефакторинг - серьезная ответственность. Не все могут ее на себя взять. Плюс надо убедить менеджмент, а менеджмент бывает разный. Плюс может не быть специалистов, которые могут провести такой рефакторинг.
Великобратов Максим
А я вообще не понимаю, что можно писать, чтобы сесть в такую лужу?
Это только в форумах все специалисты без страха и упрека. В реальных проектах люди ошибаются.
Максим Моторный
Алена, а внедрение зависимостей не стали использовать?
Никогда не использовала, нет.
скажу банальную вещь, но при разработке встраиваемых систем я не доверяю даже тому, что по стандарту глобальные переменные инициализируются нулями, просто потому что я могу подменить стартап-код. В результате все глобальные переменные я инициализирую (даже нулями) при помощи *_init() функций из каждого модуля. Может ли этот подход решить конкретную описанную проблему я на 100% не уверен, но по крайней мере это может оказаться минимальным workaround.
Подскажите, пожалуйста, альтернативу глобальным статическим переменным в следующей ситуации.
Классы биндятся в скрипт. Каждый класс знает о том, какие его члены нужно связать. Само связывание происходит в свободной функции а ее вызов идет с помощью примерно такого кода:
const bool is_registered = BindFunction()
Если вызывать эти функции для каждого класса вручную в стороннем модуле, то получается не так "красиво" чтоли...
Также, отдельно есть следующая проблема:
- если поместить этот код в *.h файлы, то он вызовется многократно, что является излишним
- если же его поместить *.cpp файлы, то нет гарантии что он будет вызван вообще и я не знаю как сделать этот вызов гарантированным
Заранее спасибо и извините что в формате вопроса, хотя думаю информация по теме и может быть кому-то интересна.
такие переменные надо возвращать через функцию, и все будет пучком
bar& foo() { static bar& v; return v; }
Для этого счетчик шварца вполне подойдет.
2 Александр
Альтернатива, например, все же поместить инициализацию в *.h файлы, но применить счетчики Шварца, уже упомянутые выше, чтобы не вызывать ее многократно.
>Они как-то самопроизвольно начинают друг от друга зависеть.
ошибки проектирования
фсё ф топку
> фсё ф топку
А проекту уже 3 года (это хорошо, если 3). Переписать с нуля - много денег.
Никто тут про это пока не говорил, но для полей класса общих для всех членов (static) возникает та же проблема. А без них бывает трудно обойтись.
Все знают, как писать хороший код, но никто не пишет:) Но никто не знает, что делать с существующим кодом :) Ну вот есть оно, соглашусь, что кривое, но его 300+ мегов(реальный проект) и месяц времени... будем ругать архитектуру и переписывать с нуля? Успехов...
А теперь по делу.
Порядок инициализации зависит от порядка объектных файлов, передаваемых линкеру. По крайней мере на юниксах. Он может быть прямой или обратный. Просто напишите тестовое приложение и поиграйтесь.
Затем найдите в проекте объектные файлы с нетривиальными глобальными объектами. Под линуксами в таких объектниках присутствует функция `__GLOBAL_sub_ что-то там`... Остальные объектники на результат не влияют.
Утилитой `nm` можно получить список импорта экспорта каждого объектника. Глобальные переменные лежет в секции `T`. Соответственно импорт обозначен как `t`
Строим граф зависимостей объектых модулей. Если А импортирует что-то из Б, значит А зависит от Б и Б должен инициализироваться раньше. Существует вероятност циклического графа, если объект используется вне секции глобальной инициализации, такие случаи правятся ручками.
Делаем топологическую сортировку графа. Инвертируем массив если нужно.
Линкуем бинарник в нужном порядке.
Для красоты для все эти действия реализуем в скриптах и заносим всю процедуру в мейкфайл.
Это работает уже 5 лет в проектах по 4М строк кода.
Успехов.
Отправить комментарий