I have global variables whose constructors rely on other global variables in different translation units. My understanding is that the order of initialization of global variables is unspecified, so this isn't safe.
How can I ensure that the globals I need will be initialized at the latest when they are first accessed? For instance, if I create a static variable in a function and call that function to get a reference, will it always be initialized the first time the function is executed?
In C language both the global and static variables must be initialized with constant values. This is because the values of these variables must be known before the execution starts. An error will be generated if the constant values are not provided for global and static variables.
Global variables within each translation unit are constructed in order of their declaration. The order between translation units is, indeed, not specified. They're not even required to be "constructed" (dynamically initialized) before entering main , see [basic. start.
Global and static variables are initialized to their default values because it is in the C or C++ standards and it is free to assign a value by zero at compile time. Both static and global variable behave same to the generated object code.
You can use the same method as that used for standard streams std::cout
and its friends. It is called Schwarz Counter or Nifty Counter.
If you look into ios_base.h
header of GNU libstdc++
:
// 27.4.2.1.6 Class ios_base::Init
// Used to initialize standard streams. In theory, g++ could use
// -finit-priority to order this stuff correctly without going
// through these machinations.
class Init
{
friend class ios_base;
public:
Init();
~Init();
private:
static _Atomic_word _S_refcount;
static bool _S_synced_with_stdio;
};
And into iostream
header:
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
extern istream cin; /// Linked to standard input
extern ostream cout; /// Linked to standard output
extern ostream cerr; /// Linked to standard error (unbuffered)
extern ostream clog; /// Linked to standard error (buffered)
// For construction of filebuffers for cout, cin, cerr, clog et. al.
static ios_base::Init __ioinit;
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
And into ios_init.cc
:
ios_base::Init::Init()
{
if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
{
// Standard streams default to synced with "C" operations.
_S_synced_with_stdio = true;
new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);
// The standard streams are constructed once only and never
// destroyed.
new (&cout) ostream(&buf_cout_sync);
new (&cin) istream(&buf_cin_sync);
new (&cerr) ostream(&buf_cerr_sync);
new (&clog) ostream(&buf_cerr_sync);
cin.tie(&cout);
cerr.setf(ios_base::unitbuf);
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 455. cerr::tie() and wcerr::tie() are overspecified.
cerr.tie(&cout);
// NB: Have to set refcount above one, so that standard
// streams are not re-initialized with uses of ios_base::Init
// besides <iostream> static object, ie just using <ios> with
// ios_base::Init objects.
__gnu_cxx::__atomic_add_dispatch(&_S_refcount, 1);
}
}
ios_base::Init::~Init()
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_S_refcount);
if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, -1) == 2)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_S_refcount);
// Catch any exceptions thrown by basic_ostream::flush()
__try
{
// Flush standard output streams as required by 27.4.2.1.6
cout.flush();
cerr.flush();
clog.flush();
}
__catch(...)
{ }
}
}
The above embeds a global object __ioinit
with static storage duration into each translation unit (.o) that includes iostream
header file. I.e. each .o
gets its own copy of __ioinit
.
All objects of fundamental types with static storage duration are zero-initialized on start-up during static initialization phase (on Linux this is .bss
section of an elf object), hence _S_refcount
is assigned 0 prior to dynamic initialization phase.
Next, during dynamic initialization phase constructors of these __ioinit
objects are called. Each constructor increments _S_refcount
and the __ioinit
object that observes 0 value of _S_refcount
resides in the translation unit that is being initialized first. This object's constructor initializes the standard streams.
There is more information in C++ Standard Library Defect Report List Issue 369: io stream objects and static ctors.
You can use the same method to initialize your own global objects. E.g.:
// DynamicInitializer.h
template<class T>
struct DynamicInitializer
{
// These members have to be POD types to be zero-initialized at static initialization phase
// prior to the dynamic initialization phase which invokes constructors of global objects.
static T* instance_;
static unsigned ref_count_;
DynamicInitializer() {
if(!ref_count_++)
instance_ = new T;
}
~DynamicInitializer() {
if(!--ref_count_)
delete instance_;
}
operator T&() const { return *instance_; }
T* operator->() const { return instance_; }
DynamicInitializer(DynamicInitializer const&) = delete;
DynamicInitializer& operator=(DynamicInitializer const&) = delete;
};
template<class T>
unsigned DynamicInitializer<T>::ref_count_ = 0;
template<class T>
T* DynamicInitializer<T>::instance_ = 0;
Usage:
// MyLogger.h
struct MyLogger
{
void log(char const*);
};
// const makes static storage.
DynamicInitializer<MyLogger> const my_global_logger;
Now, whenever MyLogger.h
is included, my_global_logger
is guaranteed to be initialized before its first use, e.g. my_global_logger->log("hello");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With