Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up


Find what you need - explore our website and developer resources
Have you ever encountered a bug which is experienced by and also caused by code running before main() even starts? This can happen if you have a static variable or singleton of a class. Each constructor of such classes gets called in order. But what order?
Turns out, this order is undefined by design. And this actually has a name:
Static Initialization Order Fiasco
This mentions code depending on other code. I certainly don't recommend ever intentionally designing an application with such dependencies. But... it can still happen. That's when this knowledge is useful.
When the program compiles and links, a table of function pointers to all the static methods which need to be called to initialize the static variables is created.
Then, the MainCRTStartup function loops over these pointers and calls them. Again, all this happens before main().
With this background info, we can be more specific in formulating the problem:
Wouldn't it be great if we could examine the "MainCRTStartup"?
Microsoft actually supplies source code for this! It can be found in the file exe_common.inl which in my case was found at "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.39.33519\crt\src\vcruntime\exe_common.inl"
The call to _initterm performs this initialization (line 17):
bool has_cbool has_ctor = false;
__try
{
bool const is_nested = __scrt_acquire_startup_lock();
if (__scrt_current_native_startup_state == __scrt_native_startup_state::initializing)
{
__scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT);
}
else if (__scrt_current_native_startup_state == __scrt_native_startup_state::uninitialized)
{
__scrt_current_native_startup_state = __scrt_native_startup_state::initializing;
if (_initterm_e(__xi_a, __xi_z) != 0)
return 255;
_initterm(__xc_a, __xc_z); // <--- THIS LINE (256)!
__scrt_current_native_startup_state = __scrt_native_startup_state::initialized;
}
else
{
has_cctor = true;
}
__scrt_release_startup_lock(is_nested); Depending on the debugger, this might be out of scope for your IDE. This was the case for me.
It's assumed you can either use a regular debug build or a release build with generated PDB file (/Zi flag to generate separate PDB for MSVC compiler).
Ensure entry breakpoint is set in the options dialog:
This automatic breakpoint will pause the debugging at the program entry point - not main() but the actual entry point.
There is a lot to look at in this screenshot. The program is called gltf_render_pbr. Use the step-into button in the debugger until you reach the correct "file" - it will tell you on the right hand side. Look at the very right for exe_common.inl
Now you can find the correct line for the breakpoint. It is marked in red here. Also notice the __xc_z and __xc_a, those are the start and end pointers of the table where the static initializer function pointers are stored. Note the little red dot to the left of the red highlighted address? That means I have a breakpoint set on that instruction. The call <gltf_render_pbr._initterm> instruction. This is the one jumping into the actual performing code inside ucrtbased.dll.
When you step into that, you will end up here:
This is the loop which calls each initializer in the list.
The actual call into each initialization method is: call qword ptr ds:[7FFCD4B6BBA0].
Note that the first static method the "pre_cpp_initialization" step in the CRT code, exe_common.inl:216
It initializes exception handlers and other things.
The next call should be into actual C++ initializers. If you have debug symbols, you should be able to see where you end up. In the example program I used - fmt had a static method, and then the next after that was my test code.
When you restart the program in the debugger, each DLL will also have its own CRT startup sequence. Thus:
_initterm callAbout KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Learn Modern C++
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
Learn more
1 Comment
18 - Dec - 2025
Ilya
Good one! Actually there is a way to do at least an attempt at ordering: C++20 constinit. It is initialized before the other static data, and it does not have to be constexpr.