开发者

C++ global initialization order ignores dependencies?

开发者 https://www.devze.com 2023-01-16 14:33 出处:网络
I think my problem is best described in code: #include <stdio.h> struct Foo; extern Foo globalFoo;

I think my problem is best described in code:

#include <stdio.h>

struct Foo;

extern Foo globalFoo;

struct Foo {
    Foo() {
        printf("Foo::Foo()\n");
    }

    void add() {
        printf("Foo::add()\n");
    }

    static int addToGlobal() {
        printf("Foo::addToGlobal() START\n");

        globalFoo.add();

        printf("Foo::addToGlobal() END\n");

        return 0;
    }
};

Foo globalFoo;

int dummy = Foo::addToGlobal();

int main() {
    printf("main()\n");

    return 0;
}

The above prints (with gcc 4.4.3):

Foo::Foo()
Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
main()

This is what I expect, and seems logical.

However, when I swap the following lines:

Foo globalFoo;
int dummy = Foo::addToGlobal();

into this:

int dummy = Foo::addToGlobal();
Foo globalFoo;

the program outputs the following:

Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
Foo::Foo()
main()

It seems instance methods of Foo are being called using an instance which has not yet been constructed! Something as simple as moving the declaration of a variable in the global scope is affecting the behaviour of the program, and that leads me to believe (1) the order of initialization of globals is not defined and (2) the 开发者_JAVA技巧order of initialization of globals ignores all dependencies. Is this correct? Is it possible to make sure the constructor of Foo is called before initializing dummy?

The problem I am trying to solve is filling a repository of items (a static instance of Foo) statically. In my current attempt, I am using a macro which (among other things) creates a global variable (in an anonymous namespace to avoid name clashing) whose initialization triggers the static initialization. Perhaps I'm tackling my problem from the wrong angle? Is there a better alternative(s)? Thanks.


(1) the order of initialization of globals is not defined

Global variables in a single translation unit (source file) are initialized in the order in which they are defined.

The order of initialization of global variables in different translation units is unspecified.

(2) the order of initialization of globals ignores all dependencies

Right.

Is it possible to make sure the constructor of Foo is called before initializing dummy?

Yes, if globalFoo is defined before dummy and they are in the same translation unit.

One option would be to have a static pointer to the global instance; such a pointer will be initialized to null before any dynamic initialization takes place; addToGlobal can then test whether the pointer is null; if it is, then it is the first time the global is being used and addToGlobal can create the global Foo.


On the order of initialization, read the answer here.

On how to solve the initialization issue, you can push the global to be a static local variable in a function. There standard guarantees that the static local variable will be initialized in the first call to the function:

class Foo {
public:
   static Foo& singleton() {
      static Foo instance;
      return instance;
   }
};

Then your other global variables would access the variable as:

Foo::singleton().add();

Note that this is not generally considered as a good design, and also that even if this solves the initialization issues, it does not solve the order of finalization, so you should be careful not to access the singleton after it has been destroyed.


C++ lacks anything like Ada's pragma elaborate, so you can't make any assumptions whatsoever about the order initilizations will happen in. Sorry. It sucks, but that's the design.


The most reliable way to provide correct init order for globals...

1) Init order depends on object files order passed to linker. Straight or reverse -not matter. You may create test application to detect it.

2) Use appropriate utilities( nm for example ) to discover imports & exports for each object file that contains globals.

3) Build the dependencies graph, sort object files and build required order for correct linking. Resolve cycles manually if exists.

I use such procedure in my makefiles on Linux. It works...


You're correct, initialization of globals between translation units is undefined. It is possible to get around this using the singleton pattern. However, be warned that this design pattern is often misused. Also be warned that order or destruction of globals is also undefined, in case you have dependencies in the destructors.


How about having the static global variable be a pointer initialized to nullptr. Then before another global object tries to use the object, check for its creation and create if needed. This worked for me for creating a global registry of class creators where one could add new classes without changing the file that handled the registry. i.e.

class Factory {
   static map<string, Creator*>* theTable;
   static void register1(const string& string, Creator* creator);
...
};
...
map<string, Creator*>* Factory::theTable= nullptr;
void Factory::register1(const string& theName, Creator* creator) {   
    if (!theTable) theTable=new map<string, Creator*>;
    (*theTable)[theName]=creator;
}

This compiled and worked with VC++ in Visual Studio 2015.

I had tried using before this

class Factory {
    public:
      static map<string, Creator*>  theTable;
      static map<string, Creator*>& getTable();
      static void register1(const string& string, Creator* creator);
}
map<string, Creator*>  Factory::theTable;
map<string, Creator*>& Factory::getTable() {
   return theTable;
}
void Factory::register1(const string& theString, Creator* creator) {
   getTable()[theString]=creator; // fails if executed before theTable is created

}

but I still had exceptions thrown when theTable was not created before trying to insert an entry into the map, which can happen if the registration of the class is handled in a separate compilation unit from the factory logic.


Global variables in a single translation unit (source file) are initialized in the order in which they are defined.

It is important to add the note to this rule that the mere declaration does not define the order:

extern Foo globalFoo; // or just a ref that is defined at a single place
extern Foo & globalFooRef;

or as a static member

struct Global
{
    static Foo globalFoo; // or just a ref that is defined at a single place
    static Foo & globalFooRef; 
};
0

精彩评论

暂无评论...
验证码 换一张
取 消