How can I guarantee that global variables will be initialized in the correct order?

I have global variables whose constructors depend on other global variables in different translation units. I understand that the order of initialization of global variables is unspecified, so this is not safe.

How can I make sure that the global variables that I need will be initialized no later than when they are first available? For example, if I create a static variable in a function and call this function to get the link, will it always be initialized when the function is first run?

+5
source share
1 answer

You can use the same method as for standard streams std::cout and its friends. It is called a Schwarz counter or a Nifty counter .

If you look in the ios_base.h GNU libstdc++ header:

 // 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 in the 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 in 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 __ioinit object with a static storage duration in each translation unit (.o) that includes the iostream header file. That is, each .o gets its own copy of __ioinit .

All objects of fundamental types with a static storage duration are initialized to zero at startup during the static initialization phase (in Linux, this is the .bss section of the elf object), therefore _S_refcount to 0 before the dynamic initialization of the phase.

Next, during the dynamic initialization phase, the constructors of these __ioinit objects are __ioinit . Each constructor increments _S_refcount , and the __ioinit object that observes the value 0 _S_refcount is located at the beginning of the translation. This object constructor initializes standard threads.

The C ++ Standard Library error report list contains additional information. Problem 369: io stream objects and static ctors .


You can use the same method to initialize your own global objects. For instance:.

 // 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; 

Using:

 // MyLogger.h struct MyLogger { void log(char const*); }; // const makes static storage. DynamicInitializer<MyLogger> const my_global_logger; 

Now that MyLogger.h enabled, my_global_logger guaranteed to be initialized before its first use, for example. my_global_logger->log("hello");

+6
source

Source: https://habr.com/ru/post/1233088/


All Articles