TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #277 3.95K
​Static initialization order fiasco

Добрались мы наконец-то до этого мерзопакостного явления. По сути, про статики мы говорили ради нескольких тем и эта одна из них.

В чем суть. Как вы уже поняли, что с порядком инициализации у статиков все очень плохо. Но внутри одной единицы трансляции он хотя бы определен и предсказуем! С божественными способностями предсказания, конечно. Ну или с томиком стандарта и нашими статьями под рукой. Но он этот порядок хотя бы какой-то есть. Один раз нормально сделай и можно надеяться на обратную совместимость языка, что все будет работать как надо.

Но вот между разными юнитами трансляции порядок вообще не определен.

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
  • 👍 18
  • ❤‍🔥 6
  • 🫡 4
  • 🔥 1
  • 😁 1
  • 🤯 1
More from @grokaemcpp
  1. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  2. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  3. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  4. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  5. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  6. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →