17

Consider the following example:

  • tt.h declares a global constant with external linkages extern int g_TRAGIC;

  • tt.cpp defines g_TRAGIC as follows const int g_TRAGIC = 0xF001;

  • my.cpp wants to use it to define its own global constant const int g_MAGIC = g_TRAGIC;

As I read the iso-FAQ I would assume that this results in static initialization order fiasco. However the iso-FAQ notes

The static initialization order fiasco can also, in some cases, apply to built-in/intrinsic types.

What does that some cases mean? Under which conditions are we save and sound from SIOF for built-in/intrinsic types, in particular constants? Or must that Construct On First Use Idiom be used for all constants with external linkage?

Note: In the real code I can not change the definition of g_TRAGIC.

Cœur
  • 32,421
  • 21
  • 173
  • 232
Lothar
  • 830
  • 6
  • 21
  • 1
    Is there a reason you can't define `g_TRAGIC` in the header file only? C++ does allow that and would avoid the initialization order fiasco. – pcarter Feb 25 '16 at 15:47
  • good point. theoretically yes, in practice that code is outside my control. – Lothar Feb 25 '16 at 21:15
  • You'll be safe when modules become standard C++. – edmz Feb 25 '16 at 21:37
  • Is there any reason to have `g_MAGIC` at all? – user207421 Feb 25 '16 at 21:47
  • Yes, `g_MAGIC` is part of an interface we provide. `g_TRAGIC` is an implementation detail outside my control. – Lothar Feb 26 '16 at 09:25
  • presumably "some cases" just refers to those cases where it is a problem. You described one such case in your post. A case where it is not a problem would be where both constants were defined in the same unit – M.M Feb 27 '16 at 10:50

2 Answers2

1

Compilers can produce different sorts of code.

Static initialized data segment

The compiler emits into a data section a name and its initial value.

.data
   dw myData   6

This is initialized at compile time, and is safely defined throughout the life of the program

constructed data

Another alternative is for the compiler to reserve some space for the variable, and create an initializer/constructor for the data, and then call the constructor just before main. With the destructor (if required) being performed atexit.

 class CriticalSection {
      CRITICAL_SECTION m_myCS;
      public:
         CriticalSection() {
              InitializeCriticalSection( &m_myCS );
         }
         ~CriticalSection() {
              DeleteCriticalSection( & m_myCS );
         }
 } cs;

combined

Some data may be performed in both stages.

 struct Data {
     bool initialized;
     void *(*pMalloc)( size_t size );
 }  FixMalloc = { true, MyMalloc };

I have seen compilers (VS2013) produce code which initializes initialized to true in the static data, but creates a function to assign pMalloc to MyMalloc at runtime. (This was because there was not a known constant for MyMalloc.)

singleton method

 SomeClass * GetSomeClass()
 {
     static SomeClass cls;
     return &cls;
 }

This is order defined - when it gets called, but requires a fully C++11 compiler to be thread safe.

Summary

The guarantees are :-

  1. statics in the same compilation unit are initialized top-to-bottom.
  2. statics are initialized single threaded.
  3. singletons have defined order of construction. But not necessarily thread safe.

The guarantees are not :-

  1. All of an object is initialized at the same time.
  2. Static initialization has a working runtime.

Before main is called, both your statics and the C/C++ runtime are bootstrapping. The order of construction issues also occur between your code and the runtime, and so you may construct some items with complex constructors which rely on services which can't be available.

mksteve
  • 11,552
  • 3
  • 24
  • 45
  • After rereading both your answer and the C++ FAQ I conclude that # Sometimes / often the compiler initializes builtin / intrinsic types just fine, even when other types do not. # This is compiler dependent and you should no rely on that. # Defining constants in C++ is an acquired taste. I will accept your answer pointing out the "combined" case gives a good clue. – Lothar Mar 23 '16 at 13:58
0

Further reading of iso-FAQ give us an answer.

SIOF happens if you try to initialize builtin/intrinsic type by return value of constant function.

const int g_MAGIC = f(g_TRAGIC);