beshers@select.columbia.edu (Clifford Beshers) (10/25/88)
I got several responses to my query about the calling order of global constructors. Since it seems to be and important issue, I provide a summary here. The problem was, I referenced 'cout' in a global constructor and the program dumped before the first line of 'main()', indicating that my global constructor had been called before cout's. I compiled on a Sun4 with AT&T CC. This problem does not occur on all compilers. The patches seem to be trivial for different versions of the AT&T compiler. Here are the reasons why: ************* From: patrick@cs.cornell.edu (Pat Stephenson) Subject: Global constructors and C++ I/O streams; problems and questions. It was my interpretation of "the book" (sorry, reference not to hand) that all library constructors should be called before any user code (including global constructors). So I ran across this problem too. I was using cfront 1.2 and the CC script distributed with it, under BSD. If you're not, this will be of no help to you. CC finds the required global constructors by loading the user code and using a program called "munch" on nm output to figure out what constructors need to be called. munch generates an extra bit of C code that calls the constructors and destructors. munch appears to assume that its input is sorted numerically, which is not the default under BSD (it may be under sys5, I don't know). So just arrange for the nm output to be sorted numerically. I just piped the nm output through sort, ie replace the line: nm $NMFLAGS $OO | $munchC $P >__ctdt.c with nm $NMFLAGS $OO | sort | $munchC $P >__ctdt.c in CC. I suppse there is an nm flag that would have the same effect. This worked for me. Pat ************ A similar solution follows: ************ From: tolerant!procase!ajs@ucbvax.berkeley.edu (Andrew Jay Schneider) Subject: Static constructors... You're correct, the order in which the static object's constructors get called is important. In the ``patch'' versions of the compiler a hueristic is used to order the constructors so that objects in libC.a get called ahead of those in the user's code. In the ``munch'' versions I don't think this ordering takes place. ... In this scheme, the constructor for the stream object would be called ahead of the one in your code. In fact, when I compiled your example with a ``patch'' compiler it did run correctly. Using our older ``munch'' compiler broke it. ... In looking at the CC shell script I found that you can pass parameters down to nm(1) via an environment variable. Setting the variable NMFLAGS to -n (% setenv NMFLAGS -n) made the program run correctly. Andy Schneider tolerant!procase!ajs@ucbvax.berkeley.edu (I think) ************ Some comments on what the compiler *should* be doing. ************ From: prl@iis.UUCP (Peter Lamb) ... Although the streams and other libC classes should work properly, you are quite right in believing that initialisation of static objects whose correct initialisation depends on the initialisation of other static objects is something that is *very* hokey in C++. It _is_ very useful; the problem is that the Unix, and most other, loaders are too dumb. Some rules of thumb for doing this are (this assumes BSD4.[23]. I am not familiar enough with the finer points of the SysV ld and loader utilities to know if all of these things will work there): 1) Don't overuse the feature and try to avoid (where possible) static objects that depend on the initialisation of static objects in other files. 2) Use explicit loads of object modules rather than libraries. You have more control over them. The BSD loader and munch together will ensure that the static constructors in the last-mentioned files (and libraries) will be executed before those in the earlier modules. Static destructors will be called for the object files left-to-right. 3) If you need to put interdependent static objects in libraries use the old Unix v7 commands lorder and tsort to try to persuade the objects to be put into the library in the correct order: eg, for make: universal_class_lib.a: $(OBJECTS) rm -f universal_class_lib.a ar crv universal_class_lib.a `lorder $(OBJECTS) | tsort` ranlib universal_class_lib.a # This isn't needed for SysV If tsort doesn't complain about cycles in the data, the library will probably load in the correct order. If you can't get this to work, you will have to order the library by hand, or prehaps rearrange your objects in their files. 4) The order of initialisation between object files is *not* defined as part of the language. So none of the above need necessarily work. Static constructors should be able to do IO through streams, and the mechanisms which allow this will probably mean that the other techniques will work as well. It is a great pity that this can't be done *much* better, but Unix (and most other) loaders are just too dumb (there are good reasons for loaders to be dumb, too, unfortunately...). -- Peter Lamb uucp: seismo!mcvax!ethz!prl eunet: prl@ethz.uucp Tel: +411 256 5241 Institute for Integrated Systems ETH-Zentrum, 8092 Zurich ************ A more pessimistic view. ************ From: bright@Data-IO.COM (Walter Bright) Morals: 1. Don't use stream I/O in constructors. 2. Don't depend on the order in which module constructors are called. Definition: Module constructor: the function inserted by the C++ compiler into each compilation unit (module). This function calls all the static constructors for that module, and also does any static initialization that cannot be done at compile-time. ************ Summary: The bug was caused by 'munch' getting unsorted input from 'nm' in the CC script. The solutions were a) pipe the output of nm through sort; b) use a flag (-p or -n depending on your system) to 'nm' to have it do the sorting; or c) use patch (a later version of munch) which doesn't rely on sorted input. The compiler implementation seems responsible for coping with the ordering of global constructor invocations; the language only specifies the ordering within a single module, not between modules and libraries. The AT&T compiler chooses to order the invocations back-to-front, i.e. the modules or libraries later in the command line are called before those earlier. (That is, with the above patch applied.) Since the problem seems very system and loader dependent, there is not formal specification in the language. The best we can expect is that all compilers document their ordering methods clearly. Cliff Beshers Columbia University Computer Science Department beshers@sylvester.cs.columbia.edu
jss@hector.UUCP (Jerry Schwarz) (10/25/88)
In article <5964@columbia.edu> beshers@select.UUCP (Jonathan M. Smith) writes: >I got several responses to my query about the calling order of global >constructors. Since it seems to be and important issue, I provide >a summary here. > >The problem was, I referenced 'cout' in a global constructor and >the program dumped before the first line of 'main()', indicating >that my global constructor had been called before cout's. I >compiled on a Sun4 with AT&T CC. > The situation is both more and less complicated than is suggested by most of the prior responses in this group. The C++ language definition says that the constructors for static objects are called in the order they appear in a file, but gives no assurance about what order the initialization occurs between files. The "fixes" provided manage to force particular orders in particular cases in particular versions of particular compiling systems. The are special purpose and do not really solve the problem. But a technique I have developed allows the programmer to achieve the desired effect without making any implementation specific assumptions. This technique is incorporated in the iostream package that will probably be part of cfront release 2.0 and will allow you to use iostream I/O from constructors with minimal care. I will sketch it here. I more complete description is contained in a short paper that I have written. (It hasn't been formally "released" yet, but if you send me a note I'll send you a copy when it is available.) Although it may seem complicated at first, once it is understood it can be applied without much thought. At the end of iostream.h there is a declaration of a class class Iostream_init { public: static int init_count ; // initialized to 0 Iostream_init() ; ~Iostream_init() ; } ; static Iostream_init iostream_init ; Thus the constructor for Iostream_init is called at startup once for every file that includes iostream.h. Similarly, during exit processing, the destructor is called once for every time the constructor was called. The purpose of init_count is to ensure that the real initialization and finalization actions occur only once. In particular cin and cout are initialized by the Iostream_init constructor the first time it is called. (A slight complication arises because an iostream variable cannot be explicitly initialized. An ugly kludge using the preprocessor lets me get around this.) Because of its placement in the header file the Iostream_init constructor will normally be called before any use of cout or cin. In some cases explicit calls must be inserted. My style calls for initialization classes to be associated with almost all classes I define. Explicit calls to new Iostream_init must sometimes be added in the constructors for initialization classes. In particular, when a constructor of class K does I/O, but the header associated with class K does not require iostream.h to be included before the declaration of K, then a call to "new Iostream_init" must appear in the constructor of K_init before the use of any K constructor. Jerry Schwarz AT&T Bell Labs, Murray Hill
prl@iis.UUCP (Peter Lamb) (10/27/88)
In article <5964@columbia.edu> beshers@select.UUCP (Jonathan M. Smith) writes: >I got several responses to my query about the calling order of global >constructors. Since it seems to be and important issue, I provide >a summary here. ... > The AT&T compiler chooses to order the invocations >back-to-front, i.e. the modules or libraries later in the command >line are called before those earlier. (That is, with the above >patch applied.) > It may be a little counter-intuitive, but *DONT* go changing the order of the way in which munch generates it's table of constructor pointers. The constructors in libraries should get called *FIRST* (this is the problem that you were having in the first place - they weren't getting called first) Since libraries are usually somewhere down the righthand end of the load CC, their constructors need to be called before main's constructor. (This is assuming the nm command outputs the symbols for the earliest-loaded symbols first in its listing) Note: Mike Mobray, who usually gets such things right also posted a `fix' for munch to reverse the order of call of the static constructors. Don't do this in AT&T 1.2.1 C++. Munch does the right thing there. The right fix is to use nm -p (output the symbols in loader order) or nm -n (output the symbols in numerical order) in the CC script. > Since the problem seems very system and loader dependent, >there is not formal specification in the language. The best we >can expect is that all compilers document their ordering methods >clearly. True, and sadly this is typically not documented. Neither is the order in which constructors for member objects are called. (at least not in the first edition of the book) > >Cliff Beshers >Columbia University Computer Science Department >beshers@sylvester.cs.columbia.edu -- Peter Lamb uucp: uunet!mcvax!ethz!prl eunet: prl@ethz.uucp Tel: +411 256 5241 Integrated Ststems Laboratory ETH-Zentrum, 8092 Zurich