__libc_init_array and global declared objects in cpp

53 Views Asked by At

I have a micro controller c++ project where I declare as much as possible globally instead of newing objects. In fact, I am not using the new keyword anywhere.

I now ran into the situation where the order of the __libc_init_array is causing issues for me. I hope I can explain my situation well enough for somebody to point out if I am doing something horribly wrong, or if I can control the problem at hand.

I have a lot of classes, where dependencies are injected through the constructor. The objects are constructed when startup code calls into __libc_init_array. I now have one situation where the the constructor of class B is called before the constructor of class A is called, while B depends on A. So when the constructor of B uses any member of 'uninitialized' A, the app hard faults.

Is there a way to enforce the order of constructors called by __libc_init_array? Or did I make a horrible mistake relying on this behavior? Or... is it a bad idea to use members of dependencies in the constructor all together in c++?

I realize when I would new all objects I would have 100% control over this behavior construction order, but it's quite a bit of work to refactor that.

For now I added an initialize function for the single case where the dependency order is a problem, which solves the problem but doesn't feel like an appropriate solution either.

I tried to google for better solutions but keep coming up with non-related issues.

3

There are 3 best solutions below

4
Pepijn Kramer On BEST ANSWER

I meant something like this:

// from header file B.h

class A; // forward declaration

class B
{
public:
    // Inject dependency
    B(A& a) 
    {
    }
};

// from header file A.h
class A
{
};

// main.cpp
// include A.h and B.h

// if you had a function refering to global var b
// pass it in now.
void f(B& b)
{
}

int main()
{
    A a;
    B b{a}; // inject dependency on A

    // some function depending on B
    f(b);
}
0
0xbachmann On

I had a similar problem recently, and discovered it is the static initialization order fiasco. If you have these classes in separate translation units, you cannot prevent it.

I got around it, by declaring all my global classes which have dependencies in the main function and declaring them as extern in the respective header files.

// a.hpp
class A { /*...*/ };

extern A a;
// b.hpp
class B { /*...*/ };

extern B b;
// main.cpp

A a(/*...*/);  // guarantees that constructor of a is called before
B b(/*...*/);  // constructor of b
5
n. m. could be an AI On

You can place each global object in a function as a static variable, and have the function return a reference to it.

Instead of

// file x.cpp
X x;

// file y.cpp
Y y;

write:

// file x.cpp
X& get_x() { static X x; return x; }

// file y.cpp
Y& get_y() { static Y y; return y; }

Naturally you will have to replace references to x and y elsewhere with get_x() and get_y().

This guarantees that the initialization order will conform to the dependency DAG (if it's indeed a DAG; if there is a dependency cycle, all bets are off). For example, if the constructor of X needs y, it can only get y via get_y(). This guarantees that y is constructed before the constructor of X can see it.