TITLE: Order of construction/destruction of static global objects RESPONSE: Robert C. Martin (rmartin@rational.com) I'm actually following up a few posts from this thread in this one article. Nikki Locke started the thread by asking if all static objects (both global and local to a file) must be destroyed in the reverse order they were constructed. I answered "yes" (except less confidently than now). ARM 3.4 says that their destruction is done in reverse order of initialization. As you know, they must be constructed in the order they appear in a translation unit (.C file), but the order of construction across translation units cannot in general be determined. This leads to a problem if the construction or destruction of one static object needs to use an object defined in another module. ARM 3.4 p20 presents a technique for ensuring that a global object provided by a library is initialized before it is used. The example in the ARM is something like: (mylibrary.h)............................................................. class X { /*...*/ }; // or #include "X.h" extern X global; // `global' provided to users of mylibrary.h class mylibrary_counter { // `nifty_counter' static int count; public: mylibrary_counter() { if (count++ == 0) //... initialize `global' } ~mylibrary_counter() { if (--count == 0) //... cleanup `global' } }; static mylibrary_counter a_mylibrary_counter; .......................................................................... I didn't understand how it could be guaranteed that `global' is constructed before the first static counter is constructed. I thought that "initialize global" might be using an unconstructed `global'. In fact, `global' is *defintely* unconstructed at that point if "mylibrary.h" is included in the module in which `global' is defined. It seemed to me that `global' could only be a pointer that is new'ed and deleted the first time in the counter constructor. Bob Martin (rmartin@erde.Rational.COM) replied: >Jamshid: Take a close look at 3.4 of the ARM. It might take several >read-throughs to get it to sink in. (it took me a few :)) It says: > > "The initialization of nonlocal static objects in a translation unit > is done before the first use of any function or object defined in > that translation unit." > >When nifty_counter is constructed, it calls the initialization >functions which are (hopefully) in the same translation unit as the >definitions of the globals. This forces the dynamic initialization of >those globals prior to the execution of the initialization functions. You had me going for a while, Bob, but then I realized that if a compiler could do this, there wouldn't be any need for the nifty_counter trick! The problem of determining the order to construct globals across modules is very difficult and even impossible because it involves flow analysis (unless some ideas I present below about automating nifty_counters are correct). The commentary on p19 seems to let compilers writers off the hook (though I would like to know what ANSI is going to say): Note that it is not in general possible to complete dynamic initialization of every nonlocal static object before its use. It then gives an example involving a mutual dependency, which I don't think is very interesting since a mutual dependency should just be considered a programming error, the effects of which are undefined. Harald Fuchs (hf@informatik.uni-kl.de) and Richard Huff (huff@cs.cornell.edu) both replied that `global' can be constructed by the mylibrary_counter constructor using the new w/ placement operator. I didn't think "initialize global" meant actually "construct global" because then it would get constructed twice. The trick is to make sure the second constructor does not actually do anything (ditto for any data members). 's cout/cin/cerr are usually constructed twice with no ill-effect this way. While this is fine for the implementation (iostream doesn't have to be legal or portable), dealing with a 2nd constructor call would be a major hassle in general. So, global objects that can be used during the construction of other static objects should be defined as pointers. Richard then (modestly:-) proposes a change to C++ to allow the safe use of global objects, not just pointers: >I propose that extern objects >get NO automatic construction/destruction, while statics continue to >to receive such treatment. With that modification, the classic >nifty_counter trick mentioned in the ARM would be ideal. I don't agree with this proposal. First, this would probably break all C++ code. Second, it solves a problem for which a solution already exists: use global pointers. Third, it would require a lot of programs/ers who do not care about the construction order of their globals to pay the cost of manually constructing them. (Okay, that's all the standard, boring reasons). More importantly, let's not forget that "allowing easy use of nifty_counter" is not the end goal -- it's a hack. The end goal is for compilers/linkers to handle all this for us, and they probably could in many cases. At the very least, a compiler could do the nifty_counter trick itself. Hmm, now that I think about it, wouldn't that be fairly easy for compilers&linkers to do? At every extern declaration of an object, spit out a nifty_counter which jumps to a subroutine determined at link time. At every static object definition spit out a linker command that says "my nifty_counters must call my module's init() function" (the module's static objects are initialized in order by init() and init() is called before main() and doesn't allow reentrancy). For example, when creating module jam.C's init() code: // jam.C // ... extern Foo foo; // spit out "jump-subr construct_foo" Bar bar; // set linker variable construct_bar = jam.C's init() // ... I imagine even if this is possible, it's probably not worth the effort and extra code. I also imagine it can't work, since I don't know much about what compilers actually do and I'm just thinking off the top of my head. Enough thought experiments. Back to real code. Richard then presents a technique to create global objects that actually look like extern objects, so users don't have to do pointer dereferencing everytime they want to use the global. You may need to refer to his post as I've just hacked it down to the parts I want to comment on. >Here's one almost-portable approach: [he's says `almost' because buf_t is a "forced-alignment" union type big enough to hold a `globals' object] >// In a file named globals.h .... [`globals' is a class for which we want to provide a global `obj'] >class nifty { >public: [...] > static globals& obj(void) { return (globals&)*(buf.storage); } > static buf_t buf; >}; > >// As far as the user is concerned, >// it appears as if there exists an extern object >// named ``globs'' of class ``globals''. >#define globs ( nifty::obj() ) Hey, the rules were no macros. They, like, rudely invade the user's (name) space. Besides, if you're going to use a macro, you could do it just as easily with a pointer: #define globs ( *(nifty::obj_ptr) ) and not worry about alignment or casting. Same goes if you require the user to call a function. Using a function is probably a good idea, though, since users would probably prefer `obj()' to `(*obj_ptr)'. Putting the global inside a class is also a good idea so you don't pollute the user's name space. I also thought about making `obj' a reference instead of a function and initializing it with: globals& nifty::obj (*(globals*)buf.storage); but the initialization of a static reference is not guaranteed to happen before run-time. In fact, I can't think of any completely ARM conforming way to provide a nifty-counted `actual' or reference (non-pointer) global object to the user without it being constructed twice (and without macros or a function call). Any ex-obfuscated-C programmers out there want to try their hands at C++? Thanks to all the above (and Jerry Schwarz for answering my e-mail questions) and I welcome further comments. Jamshid Afshar jamshid@emx.utexas.edu This continues to be a big question mark. Would somebody who actually KNOWS how this works get on the horn and tell us about it? The ARM says that dynamic initialization of a translation unit cannot be guaranteed to take place prior to the execution of main. In fact is can be deferred until any object or function in the translation unit is used. Some folks have complained that no "sane" compiler would defer such initializations, but they forget about shared libraries. OK experts... Is it true that the dynamic initialization of a tranlation unit in a shared library CAN BE DEFERRED until the first use of an object or a function in that library occurs? -------------------------- I thought I understood the nifty_counter trick (ARM 3.4) . Now I am not so sure. I had thought that the initialization of obj1..objn caused a "use of an object or a function" in the nifty_library translation unit, and therefore caused the dynamic initialization of that translation unit to occur prior to the first use of the library. Is this what is going on? Or is there some other odd mechanism happening? [ I will try to find out the answer to this and repost this article to develop. For now, its just something to think about. - adc ]