Double initialization of a static STL container in a C++ library
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).
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 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.
You are using 2 known techiques.
(1) The "module/library/namespace" as a "device" pattern
(2) Custom type registration, with a static class.
Done something similar with "Object Pascal" and "Plain C". I have several files, each file working as a module / namespace, with typedefs, classes, functions. Additionally, each "namespace" had 2 special methods (same signature or prototype), that simulate connecting a device, and disconnecting a device. Already tryed to call those methods automatically, but executing order also went wrong.
Static, Singleton classes can become a mess. I suggest, forget using macros or preprocessor/compiler and call your initialization / finalization methods yourself.
----
mylib.hpp
----
class MyLibClass {
public:
Register(string libraryName);
UnRegister(string libraryName);
};
// don't execute the "custom type registration here"
-----
mynamespace01.cpp
-----
#include "mylib.hpp"
void mynamespace01_otherstuff() { ... }
// don't execute registration
void mynamespace01_start() {
if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace01");
}
void mynamespace01_finish()
{
if not (MyLibClass::IsRegistered("mynamespace01")) MyLibClass::UnRegister("mynamespace01");
}
-----
mynamespace02.cpp
-----
#include "mylib.hpp"
// check, "2" uses "1" !!!
#include "mynamespace01.hpp"
void mynamespace02_otherstuff() { ... }
// don't execute registration !!!
void mynamespace02_start() {
// check, "2" uses "1" !!!
void mynamespace01_start();
if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace02");
void mynamespace02_start();
}
void mynamespace02_finish(){
void mynamespace02_finish();
if not (MyLibClass::IsRegistered("mynamespace02")) MyLibClass::UnRegister("mynamespace02");
// check, "2" uses "1" !!!
void mynamespace02_start();
}
-----
myprogram.cpp
-----
#include "mynamespace01.hpp"
#include "mynamespace02.hpp"
void myprogram_otherstuff() { ... }
// don't execute registration !!!
void myprogram_start() {
// check, "2" uses "1" !!!
mynamespace01_start();
mynamespace02_start();
if not (MyLibClass::IsUnRegistered("myprogram")) MyLibClass::Register("myprogram");
}
void myprogram_finish() {
if not (MyLibClass::IsRegistered("myprogram")) MyLibClass::UnRegister("myprogram");
// check, "2" uses "1" !!!
mynamespace01_finish();
mynamespace02_finish();
}
void main () {
// all registration goes here !!!:
// "device" initializers order coded by hand:
myprogram_start();
// other code;
// "device" finalizers order inverse coded by hand:
myprogram_finish();
}
-----
Check that this code is more complex and verbose that yours, but, in my experience, is more stable.
I also add "finalizer" to "initializer", and replace identifier for "Register".
Good Luck.
精彩评论