C++ in Linux: global object initialization hell!

Started by
16 comments, last by Castaa 15 years, 7 months ago
I recently upgraded my development system to Ubuntu 8.04. Which has the upgraded version of gcc/g++ compiler compared to Ubuntu 7.10. Even though it's the same project, my global variables/objects are being initialized in a different order than they were with the old compiler. This is causing a crash before main() because there is a dependency between two globals. I can't move the global declaration and initialization these because this breaks the application when built using the older g++ version which other devs still use. I'm reading that the only way to achieve predictable global initialization order is to have all globals declared in the same file. This is painful in my case. In Linux, is there a way to force global order another way or to create and call a function before anything including main()? Or other advice from the minds here?
_________________________My game: Star Sonatahttp://www.starsonata.com
Advertisement
Don't do it?
Why can't you just have an Initialize function that you call at the top of main. That solves all the problems. That function can take care of setting up your globals in any order that they need to be setup in.
Any global variable that has an initialization that can decay to said global existing in the data segment of the executable will be safe, but you shouldn't be trying to depend on functions (constructors) being called in any predictable manner before main.

Also, a hint from the C++ Faq Lite, you could just create "init on first use" semantics for said objects. That way you can be assured that they are created before you use them (though this incurs run time overhead every time you call said object which may not be acceptable in tight loops and the like)
Bear in mind this is not just an issue affecting switching OS or even compliers. Since it is undefined between different translation units, even an update to an existing compiler could technically break this behaviour.

As KulSeran points out, relying on this order is relying on undefined behaviour.

Obviously though the probem with using an Initialise function for anything other than simple types means that you can no longer initialise via construction and your objects may therefore need to be capable of existing in an uninitialised state.

This is yet another good reason to try to avoid globals.

However, if you are stuck with an old codebase and it is impractical to thoroughly redesign, an Initialise() method is probably your best bet.
Globals arnt that bad (they just arnt fassionable currently.)
You can solve the init order though by fixing them in a function.

//someClass g_foo; //wassomeClass & g_foo(){   static someClass s_foo;   return s_foo;}


Then all you need to do is search an replace all of your refs to g_foo -> g_foo()

Warning: May not help with shut down order. Your dependences still may bite there. But this has worked for me under quite a few compilers.

The other thing I have done to fix this (required for a DLL issue), was to alloc static buffer the same size as my class, and inplace new them.

This method gives you full control over you (de)construction whil still leaving the memory in global space.

Hope this is helpful.

Armand -------------------------It is a good day to code.
It is not a question of fashion - it is a question of experienced designers working on larger and more complex projects and learning what causes problems - problems much like the one posted by the OP.

I'd agree that the Meyers Singleton you suggest is another post-problem-fix when stuck with an old codebase, but surely you'd agree that it would have been better to design in the first place to avoid the issue altogether?

The lack of control of order of destruction that the Meyers pattern above gives you can actually be far more of a problem in some cases that you seem to acknowledge above - as far as I am aware, the order of destruction of statics across translation unit boundaries is as undefined as the order of construction of plain globals, and unlike a more traditional singleton pattern, the Meyers does not give you the option to destruct manually.

Order of initialisation issues is just one of the many problems that globals can cause - others relate to code-maintenance, modularity, concurrency, debugging etc etc.
Quote:Original post by Castaa

I'm reading that the only way to achieve predictable global initialization order is to have all globals declared in the same file.


Yes, C++ does not define the order of initialization.

Quote:In Linux, is there a way to force global order another way or to create and call a function before anything including main()?


Probably, but it has nothing to do with either Linux or C++, since neither of those is aware of these problems. You may be able to specify the order while linking, but that is fragile as well.

Quote:Or other advice from the minds here?


Don't use globals? Their initialization is by definition undefined, so there really is no easy way out.

Quote:Globals arnt that bad (they just arnt fassionable currently.)


Isn't this somewhat of a counter-claim to the very existence of this thread? Mutable globals are bad, especially in C++.
You also can (but should not!) change initialization order with init_priority attribute (GCC only).
Currently you are relying on undefined behaviour of object initialisation and you should try and remove this as people in this thread have pointed out. However in regard to your question "In Linux, is there a way to force global order another way..." Yes you want to use init_priority, here is an example.

edit: ninja'd
I am using this singleton class setup for almost any class that needs to be used in a lot of places and that I need only one instance for (examples: Window, TextureLibrary, GlslLibrary, SoundLibrary, etc.):

class Window{  static Window *s_instance;  public:    static Window &initialize();    static Window &instance();    static void destroy();  private:    Window();    Window(Window const &other); // not implemented: you should not be able to copy a singleton    ~Window();};inline Window &Window::initialize(){    if (s_instance)      throw string("Window was already initialized");    return *(s_instance = new Window);}inline Window &Window::instance(){    if (!s_instance)      throw string("Window was not yet initialized");    return *s_instance;}inline void Window::destroy(){    delete s_instance;    s_instance = 0;}inline Window::Window(){    // your constructor}inline Window::~Window(){    // your destructor}


This works perfectly for me, no need for global variables this way and I am still able to destruct the Window instance in the way I want. Example usage:

int main(int argc, char **argv){    try    {      Window::initialize();      Game::initialize();    }    catch (string const &error)    {      // a fatal error occured      cerr << "Error: `" << error << "'." << endl;      return 1;    }    // game is also a singleton    Game &game = Game::instance();    // run your game!    game.run();    // when finished:    Game::destroy();    Window::destroy();    return 0;}
Quote:Original post by TheFlyingDutchman
I am using this singleton class setup for almost any class [...] that I need only one instance for

NEEDING one instance and NOT ALLOWING MORE THAN one instance is not the same. You are abusing the singleton pattern.

This topic is closed to new replies.

Advertisement