There are a few good questions and answers here around the "static initialization order fiasco", but I seem to have hit against yet another expression of it, specially ugly because it does not crash but looses and leaks data.
I have a custom C++ library and an application that links against it. There is an static STL container in the library that registers all instances of a class. Those instances happen to be static variables in the application.
As a result of the "fiasco" (I believe), we get the container filled with the application instances during application initialization, then the library gets to initialize and the container is reset (probably leaking memory), ending up only with the instances from the library.
This is how I reproduced it with simplified code:
mylib.hpp:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class MyLibClass {
static vector<string> registry;
string myname;
public:
MyLibClass(string name);
};
mylib.cpp:
#include "mylib.hpp"
vector<string> MyLibClass::registry;
MyLibClass::MyLibClass(string name)
: myname(name)
{
registry.push_back(name);
for(unsigned i=0; i<registry.size(); i++)
cout << " ["<< i <<"]=" << registry[i];
cout << endl;
}
MyLibClass l1("mylib1");
MyLibClass l2("mylib2");
MyLibClass l3("mylib3");
myapp.cpp:
#include "mylib.hpp"
MyLibClass a1("app1");
MyLibClass a2("app2");
MyLibClass a3("app3");
int main() {
cout << "main():" << endl;
MyLibClass m("main");
}
Compile the objects with:
g++ -Wall -c myapp.cpp mylib.cpp
g++ myapp.o mylib.o -o myapp1
g++ mylib.o myapp.o -o myapp2
Run myapp1:
$ ./myapp1
[0]=mylib1
[0]=mylib1 [1]=mylib2
[0]=mylib1 [1]=mylib2 [2]=mylib3
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3
main():
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 [6]=main
Run myapp2:
$ ./myapp2
[0]=app1
[0]=app1 [1]=app2
[0]=app1 [1]=app2 [2]=app3
[0]=mylib1
[0]=mylib1 [1]=mylib2
[0]=mylib1 [1]=mylib2 [2]=mylib3
main():
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=main
Here comes the question, the static vector was re-initialized, or used before initialization? Is this an expected behavior?
If I 'ar' the library as 'mylib.a' (ar rcs mylib.a mylib.o), the problem does not happen, but probably because there is only one valid order to link to the .a and it is by having the library in the last place, as for myapp1 here.
But in our real application, a more complex one with many object files and a few static (.a) libraries sharing a few static registries, the problem is happening and the only way we managed to solve it so far is by applying '[10.15] How do I prevent the "static initialization order fiasco"?'.
(I am still researching in our somewhat complex build system to see if we are linking correctly).
If you give vector<string>
a custom constructor you will see, that it is indeed called only once, but in myapp2
you are using registry
uninitialized first, then it gets initialized ("removing" everything that's inside) and then filled again. That it doesn't segfault is just luck :)
I can't tell which part of the standard says something about this behaviour, but IMHO you should /never/ let static variables depend on each other. You might use a Meyers singleton for example for registry.
One way to work around initialization order problems is to move the static variables from global scope to local scope.
Instead of having a registry
variable within the class, put it into a function:
vector<string> & MyLibClass::GetRegistry()
{
static vector<string> registry;
return registry;
}
In the places where you would have used registry
directly, have it call GetRegistry
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With