2

My question pertains to the order of initialization of global objects. It is discussed to some extent here: C++: When (and how) are C++ Global Static Constructors Called?

What I would like to know is, how is it guaranteed that some system library objects (can't think of an example though) are initialized before they are used in application? (I know the solution is to wrap such object in a function, but I want to understand how it is dealt in compilers today)

This is also discussed in the book "linkers and loaders" by John Levine, while talking about .init and .finit sections, but the current state of art is not clear.

Community
  • 1
  • 1
Chethan
  • 905
  • 1
  • 10
  • 18

3 Answers3

5

An example would be cout.

The compiler doesn't really guarantee that those objects are built before they are used. Take this code:

struct Foo
{

    Foo();
}foo;

#include <iostream>

Foo::Foo()
{
    std::cout << "Hello World";
}

int main() {}

It segfaults because the foo object gets built before cout. When it tries to use cout, bad things happen.

Basically, all the global objects will get code inserted to construct them which runs before main. Once you are in main you are safe, all objects are built. Before that it depends on the order in which they are defined. That's why I can break it by including iostream after declaring the foo global.

Winston Ewert
  • 44,070
  • 10
  • 68
  • 83
  • Dynamic init can happen after the start of main, so long as it is before the first use. – Pubby Jan 03 '12 at 06:24
  • isn't cout defined in a different translation unit? So, even if you include iostream before defining Foo, isn't the order not guaranteed? – Chethan Jan 03 '12 at 06:25
  • @Pubby, not aware of that, got a reference? – Winston Ewert Jan 03 '12 at 06:28
  • @Chethan, don't know what the standard says on that. My implementation has a static __ioinit object in `iostream` which I presume is responsible. – Winston Ewert Jan 03 '12 at 06:31
  • @DietmarKühl, the above code does segfault on `gcc version 4.4.3`. – Winston Ewert Jan 03 '12 at 06:34
  • 2
    @Chethan: It does matter. Compilers have tricks to handle the issue with initialization and iostreams. One common pattern is to have a private type that counts the number of instances of the type that are created and have a namespace level variable of that type defined in `iostream` inside a private namespace. When the constructor for those objects is run as part of static initialization of the TU it checks whether it is the first object and in that case it initializes the iostreams. Because those objects are in all translation units that include `iostream` the order of execution is guaranteed – David Rodríguez - dribeas Jan 03 '12 at 06:36
  • @DietmarKühl: Uhm... *Is any contemporary standard C++ library indeed silly enough to use the "Init"-approach to initialize the global objects?* Simple answer: YES. Complex answer I don't think you can get away without it, but I would be happy to hear suggestions as to how to guarantee initialization of `cout` without *any* static initialization and without incurring on too big a cost. – David Rodríguez - dribeas Jan 03 '12 at 06:38
  • @DietmarKühl, does the standard guarantee I've got access to cout when I do it like that? I assumed I was hitting undefined behavior, and they aren't likely to be interested in changing it, especially with such a contrived example. – Winston Ewert Jan 03 '12 at 06:43
  • @DietmarKühl, are you sure it hits those pages? Since the constructor is in the standard library, there isn't any code in the translation unit to call. – Winston Ewert Jan 03 '12 at 07:15
2

Compiler attributes can be used:

In Standard C++, objects defined at namespace scope are guaranteed to be initialized in an order in strict accordance with that of their definitions in a given translation unit. No guarantee is made for initializations across translation units. However, GNU C++ allows users to control the order of initialization of objects defined at namespace scope with the init_priority attribute by specifying a relative priority, a constant integral expression currently bounded between 101 and 65535 inclusive. Lower numbers indicate a higher priority.

Some_Class  A  __attribute__ ((init_priority (2000)));
Some_Class  B  __attribute__ ((init_priority (543)));

I presume under 101 is reserved for the implementation, for things like cout.

http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • 1
    MSVC has a similar feature (`#pragma init_seg`) that it uses for the run time library initialization. http://msdn.microsoft.com/en-us/library/7977wcck%28v=VS.100%29.aspx – Timo Jan 03 '12 at 06:33
0

There are a few approaches to make sure global variables are initialized prior to their use. However, these tend to be platform specific and the details differ between system. A typical example of global objects which should be initialized before any user code, including global variable initialization, is run are the global stream objects, in particular std::cout. There are three approaches I have played with to make sure that std::cout is initialized early:

  1. The standard library already defines std::ios_base::Init which counts how often it was constructed. By having a static std::ios_base::Init _Init; in the header <iostream> the global stream objects can be initialed using placement new when the counter is changed from 0 to 1 and explicitly destroyed when the counter changes from 1 to 0 during destruction. To avoid double initialization the definition of std::cout would just reserve the necessary space using e.g. char std::cout[sizeof(_Standard_output_stream)];. This approach can break if user code defines objects prior to including <iostream>.
  2. When loading shared libraries some initialization code is run before the objects in the shared library become available (likewise some destruction code when unloading a shared library). By putting the corresponding objects into a shared library the initialization code can be run before the object can be accessed. The main drawback is that this requires the use of shared libraries which isn't always desirable.
  3. The linker is effectively building the list of initializations. On some systems it is guaranteed that the order in which the various object files are constructed is the reverse order of the object files being seen. That is, placing e.g. std::cout into the last object file in the last [C++] library causes this object to be initialized before other objects. Since users have control over the link line this isn't necessarily working.

The safest approach on this list is to put the objects into a shared library. The best approach is probably to avoid the problem by accessing the global object through a function. All the approaches require some extra work if threads can be started before the construction takes place. In addition, I'm sure there are platform specific approaches which deal with this. The general observation is that global objects are evil: try not to use them!

Winston Ewert
  • 44,070
  • 10
  • 68
  • 83
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I checked the iostream headers and 1. seems to be the case. Am curios how double initialization is avoided/handled. Can you pls elaborate it? – Chethan Jan 03 '12 at 07:44
  • If I understand it correctly you suggest having `cout` in a dynamic library, but that will not solve the issue. On the contrary, moving *everything else* to dynamic libraries might if the generated binary loads first the program/library containing `cout` and then the rest of them (which would ensure the order of initialization at load time). But the drawback is that everything with static variables would have to be in dynamic libraries and that is a luxury that you cannot always afford. – David Rodríguez - dribeas Jan 03 '12 at 13:37
  • On 3, the *problem* (not really a problem) is that this would require a close coupling between the standard library implementation and the toolchain. In particular you need to inject specific C++ knowledge into the linker, which I believe is not required now. The linker would have to detect the existence of `cout` in the program and move the initialization to the last object (or whichever place is appropriate) for it to be initialized first. Now, if you also compile a dll that uses `cout` you will be left with a similar problem again: the loader would have to detect whether the... – David Rodríguez - dribeas Jan 03 '12 at 13:41
  • ... main program has already initialized `cout` or not before initializing it while loading the dynamic library, which means that you are again injecting knowledge of the language to the loader, rather than have a language agnostic loader that just needs to find an entry point and start executing it. – David Rodríguez - dribeas Jan 03 '12 at 13:42