constinit
And The Static Initialization Order Fiasco#
Problem: The Static Initialization Order Fiasco#
Definition: Initialization Of Globals
static
localsInitialized when program flow crosses initialization the first time
Globals
Initialization in definition order per compilation unit
Initialization order of compilation units is undefined
Since 1970-01-01 (the epoch): integrals are initialized at compile time ⟶ this problem has been brought about by C++
SIOF Example: Mutex As A Global Variable (Demo Failure)#
This is a contrived example, but C++ Marketing stresses the constant initialized mutex feature a lot - so here it is
Compilation unit #1 contains a global
std::mutex
(andmain()
btw, which just pauses the process)Compilation unit #2 contains a global
std::thread
object which references the mutexBoth
the_mutex
andthe_thread
are initialized at runtime before main in undefined orderThis is undefined behavior - the thread could run before the mutex is initialized
To check, we link the program twice to reverse the order of initialization (see
code/siof-mutex/CMakeLists.txt
)
add_executable(c++11-constinit-mutex-runtime-init main.cpp other.cpp)
add_executable(c++11-constinit-mutex-runtime-init-reversed other.cpp main.cpp)
#include <mutex>
#include <thread>
#include <iostream>
extern std::mutex the_mutex;
std::thread the_thread([](){
the_mutex.lock();
std::cout << "inside critical section" << std::endl;
the_mutex.unlock();
});
int main()
{
the_thread.join();
return 0;
}
#include <mutex>
std::mutex the_mutex;
SIOF Example: Global std::string
Instances With Interdependencies (Demo Failure)#
Similar situation:
std::string my_string
(defined inmain.cpp
) depends onother_string
(defined inother.cpp
)Demo failure:
std::string
constructor already appears to do magic to paper over bad language definition⟶ both programs show correct behavior - even though ill-formed
$ ./siof-string/c++11-constinit-string-runtime-init
hello world
$ ./siof-string/c++11-constinit-string-runtime-init-reversed
hello world
add_executable(c++11-constinit-string-runtime-init main.cpp other.cpp)
add_executable(c++11-constinit-string-runtime-init-reversed other.cpp main.cpp)
#include <string>
#include <iostream>
extern std::string other_string;
std::string my_string{"hello " + other_string};
int main()
{
std::cout << my_string << std::endl;
return 0;
}
#include <string>
std::string other_string{"world"};
SIOF Example: Global Foo
Instances With Interdependencies#
Becoming even more contrived - although somewhat realistic
Foo
instances depending on each other, just likestd::string
above⟶ Demo success
$ ./siof-foo/c++11-constinit-foo-runtime-init
42
$ ./siof-foo/c++11-constinit-foo-runtime-init-reversed
708
add_executable(c++11-constinit-foo-runtime-init main.cpp other.cpp)
add_executable(c++11-constinit-foo-runtime-init-reversed other.cpp main.cpp)
#include "foo.h"
#include <iostream>
extern Foo other_foo;
Foo my_foo{42 + other_foo.number};
int main()
{
std::cout << my_foo.number << std::endl;
return 0;
}
#include "foo.h"
Foo other_foo = 666;
Solution: constinit
#
other_foo
needs to be available before someone depends on it⟶
constinit
Note that the depender cannot be
constinit
Note:
my_foo
cannot beconstinit
⟶extern
has no idea
$ ./siof-foo-solution/c++11-constinit-foo-constinit
708
$ ./siof-foo-solution/c++11-constinit-foo-constinit-reversed
708
add_executable(c++11-constinit-foo-constinit main.cpp other.cpp)
add_executable(c++11-constinit-foo-constinit-reversed other.cpp main.cpp)
#include "foo.h"
#include <iostream>
extern Foo other_foo;
Foo my_foo{42 + other_foo.number};
int main()
{
std::cout << my_foo.number << std::endl;
return 0;
}
#include "foo.h"
constinit Foo other_foo = 666;