How to find and solve dependencies between global variables
I have an application which [unfortunately] contains quite some global variables. A recent crash-at-startup lead me to the following construction:
FILE1.CPP:
ClassX globalVariableX;
FILE2.CPP
ClassY globalVariableY;
Unfortunately, the constructor of class Y uses code in which globalVariableX is used.
Until now everything went fine because (coincidentally) FILE1.OBJ was linked in before FILE2.OBJ, meaning that globalVariableX is instantiated before globalVariableY.
Last week, a totally unrelated change in other files, caused the linker to link in FILE2.OBJ before FILE1.OBJ. Now globalVariableY is instantiated first, its constructer indirectly refers to globalVariableX, and crashes because globalVariableX has not been instantiated yet.
I know I should get rid of all the global variables as much as possible (please don't start a debate about this).
But are there tools available that can help me to look up dependencies between global variables?
Or are there any tricks that I can use to see at run time if there are dependencies which I should get rid of (I was thinking about introducing a base-class for the global variables in which I could log the construction of global variables, but this is probably quite some work). Any other suggestions?
EDIT: All of your answers are very good suggestion开发者_运维技巧s on how to prevent these nasty problems. But I was actually looking for a way to find these dependencies, not removing all global variables (or replacing them with another construction). Any ideas on tools that find these dependencies?
The usual solution is to rely on initialization on first use.
In C++, you can use local static (in functions) to get this behavior. In C++0x (but already implemented in major compilers) this is even guaranteed to be thread-safe (the initialization, at least).
ClassX& GetClassX() {
static ClassX X; return X;
}
ClassY& GetClassY() {
static ClassY Y; return Y;
}
There is still one issue though: such a scheme does not detect cyclic references. It could be akin to a recursion gone mad and thus blow your stack ;)
You might want to avoid global variables altogether by using an Application Builder where your variables are constructed and passed to dependend variables to make the dependencies clear.
Have a look here: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14
[10.14] What's the "static initialization order fiasco"?
A subtle way to crash your program.
The static initialization order fiasco is a very subtle and commonly misunderstood aspect of C++. Unfortunately it's very hard to detect — the errors often occur before
main()
begins.In short, suppose you have two static objects
x
andy
which exist in separate source files, sayx.cpp
andy.cpp
. Suppose further that the initialization for they
object (typically they
object's constructor) calls some method on thex
object.That's it. It's that simple.
The tragedy is that you have a 50%-50% chance of dying. If the compilation unit for
x.cpp
happens to get initialized first, all is well. But if the compilation unit fory.cpp
get initialized first, theny
's initialization will get run beforex
's initialization, and you're toast. E.g.,y
's constructor could call a method on thex
object, yet thex object hasn't yet been constructed.
I hear they're hiring down at McDonalds. Enjoy your new job flipping burgers.
If you think it's "exciting" to play Russian Roulette with live rounds in half the chambers, you can stop reading here. On the other hand if you like to improve your chances of survival by preventing disasters in a systematic way, you probably want to read the next FAQ.
Note: The static initialization order fiasco can also, in some cases, apply to built-in/intrinsic types.
[10.15] How do I prevent the "static initialization order fiasco"?
Use the "construct on first use" idiom, which simply means to wrap your static object inside a function.
For example, suppose you have two classes, Fred and Barney. There is a global Fred object called x, and a global Barney object called y. Barney's constructor invokes the goBowling() method on the x object. The file x.cpp defines the x object:
// File x.cpp #include "Fred.h" Fred x;
The file y.cpp defines the y object:
// File y.cpp #include "Barney.h" Barney y;
For completeness the Barney constructor might look something like this:
// File Barney.cpp #include "Barney.h" Barney::Barney() { ... x.goBowling(); ... }
As described above, the disaster occurs if y is constructed before x, which happens 50% of the time since they're in different source files.
There are many solutions to this problem, but a very simple and completely portable solution is to replace the global Fred object, x, with a global function, x(), that returns the Fred object by reference.
// File x.cpp #include "Fred.h" Fred& x() { static Fred* ans = new Fred(); return *ans; }
Since static local objects are constructed the first time control flows over their declaration (only), the above new Fred() statement will only happen once: the first time x() is called. Every subsequent call will return the same Fred object (the one pointed to by ans). Then all you do is change your usages of x to x():
// File Barney.cpp #include "Barney.h" Barney::Barney() { ... x().goBowling(); ... }
This is called the Construct On First Use Idiom because it does just that: the global Fred object is constructed on its first use.
The downside of this approach is that the Fred object is never destructed. There is another technique that answers this concern, but it needs to be used with care since it creates the possibility of another (equally nasty) problem.
Note: The static initialization order fiasco can also, in some cases, apply to built-in/intrinsic types.
(From the C++ FAQ, http://www.parashift.com/c++-faq/)
精彩评论