Static initialization order fiasco
Добрались мы наконец-то до этого мерзопакостного явления. По сути, про статики мы говорили ради нескольких тем и эта одна из них.
В чем суть. Как вы уже поняли, что с порядком инициализации у статиков все очень плохо. Но внутри одной единицы трансляции он хотя бы определен и предсказуем! С божественными способностями предсказания, конечно. Ну или с томиком стандарта и нашими статьями под рукой. Но он этот порядок хотя бы какой-то есть. Один раз нормально сделай и можно надеяться на обратную совместимость языка, что все будет работать как надо.
Но вот между разными юнитами трансляции порядок вообще не определен.
Static initialization order fiasco отсылается к неопределенности в порядке, в котором инициализируются объекты со статической продолжительностью хранения в разных единицах трансляции. Если мы пытаемся создать объект в одном юните, который полагается на существующий объект в другом, то мы можем знатно утяжелить штаны, если получится так, что объект еще не существует. То есть он просто zero-инициализирован. В общем случае, поведение в такой программе неопределено.
Простейший воспроизводимый пример:
Если скомпилировать это дело как:
А если файлы передать в другом порядке:
Очевидно, что результат зависит от того, в каком порядке линкер увидит единицы трансляции. И это зашквар!
Например, GCC версии до 4.7 инициализировал единицы трансляции в обратном порядке их появления в строке компиляции. И в один момент это поведение поменялось на обратное, что с хренам поломало кучу проектов, которые были завязаны на инициализации именно в таком порядке.
Кстати, линкер инициализирирует единицы трансляции не в рандомном порядке. Есть разные способы: в алфавитном порядке, в передаваемом ему на вход порядке и так далее. То есть система есть, но у каждого она своя.
Это можно видеть даже на нашем примере:
В первом случае
Во втором случае source.cpp инициализируется первым и теперь все в правильном порядке.
На эти порядки ни в коем случае нельзя надеяться! Опять же пример с гцц говорит нам, что неследование стандарту чревато надеванием кастрюли на голову и ударами по ней поварешкой. Но для понимания процессов, это примерно так происходит.
Define the order of your life. Stay cool.
#cppcore #NONSTANDARD
Добрались мы наконец-то до этого мерзопакостного явления. По сути, про статики мы говорили ради нескольких тем и эта одна из них.
В чем суть. Как вы уже поняли, что с порядком инициализации у статиков все очень плохо. Но внутри одной единицы трансляции он хотя бы определен и предсказуем! С божественными способностями предсказания, конечно. Ну или с томиком стандарта и нашими статьями под рукой. Но он этот порядок хотя бы какой-то есть. Один раз нормально сделай и можно надеяться на обратную совместимость языка, что все будет работать как надо.
Но вот между разными юнитами трансляции порядок вообще не определен.
Static initialization order fiasco отсылается к неопределенности в порядке, в котором инициализируются объекты со статической продолжительностью хранения в разных единицах трансляции. Если мы пытаемся создать объект в одном юните, который полагается на существующий объект в другом, то мы можем знатно утяжелить штаны, если получится так, что объект еще не существует. То есть он просто zero-инициализирован. В общем случае, поведение в такой программе неопределено.
Простейший воспроизводимый пример:
// source.cpp
int quad(int n) {
return n * n;
}
auto staticA = quad(5);
// main.cpp
#include <iostream>
extern int staticA;
auto staticB = staticA;
int main() {
std::cout << "staticB: " << staticB << std::endl;
}
Если скомпилировать это дело как:
g++ main.cpp source.cpp -std=c++17
, то результат будет такой:staticB: 0
А если файлы передать в другом порядке:
g++ source.cpp main.cpp -std=c++17
, то такой:staticB: 25
Очевидно, что результат зависит от того, в каком порядке линкер увидит единицы трансляции. И это зашквар!
Например, GCC версии до 4.7 инициализировал единицы трансляции в обратном порядке их появления в строке компиляции. И в один момент это поведение поменялось на обратное, что с хренам поломало кучу проектов, которые были завязаны на инициализации именно в таком порядке.
Кстати, линкер инициализирирует единицы трансляции не в рандомном порядке. Есть разные способы: в алфавитном порядке, в передаваемом ему на вход порядке и так далее. То есть система есть, но у каждого она своя.
Это можно видеть даже на нашем примере:
В первом случае
staticB
равен нулю, потому что main.cpp стоит первым в строке компиляции и линкер инициализирует глобальные переменные этой единицы трансляции первыми. А так как на этот момент staticA
не получила своего окончательного значения, а была лишь zero-инициализирована, то staticB
инициализируется нулем.Во втором случае source.cpp инициализируется первым и теперь все в правильном порядке.
staticB
получает свое значение от уже инициализированного staticA
.На эти порядки ни в коем случае нельзя надеяться! Опять же пример с гцц говорит нам, что неследование стандарту чревато надеванием кастрюли на голову и ударами по ней поварешкой. Но для понимания процессов, это примерно так происходит.
Define the order of your life. Stay cool.
#cppcore #NONSTANDARD