Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I ensure that global variables are initialized in the correct order?

Tags:

c++

c++14

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?

like image 850
zneak Avatar asked Oct 06 '15 20:10

zneak


People also ask

How do you initialize a global variable?

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.

What is the construction order of global 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.

Are global variables automatically initialized to 0?

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.


1 Answers

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");

like image 138
Maxim Egorushkin Avatar answered Nov 09 '22 23:11

Maxim Egorushkin