Topic: initialization of static objects


Author: jba@cfdev1138x.com (Jonathan Amsterdam)
Date: Wed, 1 Sep 1993 18:29:27 GMT
Raw View
I have a question about how to initialize static objects before their first
use.  My question is, Is there a clean way to do this with current
compilers?  I'll first set up the situation, then explain why there's a
problem, then suggest one solution, which however is ugly.

My motivation is iostreams.  First, I'd like to understand how cin, cout
and cerr are correctly initialized.  Second, I've written a class derived
from ostream, and I have some global variables of that class, and I'd like
to initialize them correctly.

In the abstract, the situation is this.  We have a class C, with a global c
which we would like to be initialized before any possible use, including
uses that occur before main().  For instance, C and c may be
ostream_withassign and cout, respectively.  We begin by using the standard
trick, in which an instance of a class C_init is declared in every file
that uses C:

---------------------------------------------------------------------------
// file C.H

class C: public B {
public:
  C();
  // ...
};

extern C c;

class C_init {
  static int count;
public:
  C_init();
};

static C_init c_init;
---------------------------------------------------------------------------

Now we can be sure that C_init::C_init() is called before any use of c.
Our source file looks something like this:

---------------------------------------------------------------------------
// file C.cc

#include "C.H"

C c;

int C_init::count = 0;

C::C()
{
  // ...
}

C_init::C_init()
{
  if (count++ == 0) {
    // ...
  }
}
---------------------------------------------------------------------------

Now here is the problem: although the ARM says that c should be constructed
[using C::C()] before C_init::C_init() is called the first time, in fact
this tends not to be done.  It certainly isn't in the compiler I'm
using, the Sun Sparcworks compiler (I verified this by running the above
with output statements), and since that's a cfront compiler I assume that
no cfront correctly implements that ARM injunction.

So when C_init::C_init() executes, it is in the unusual position of having
to deal with an object that has not been constructed.  How can it cleanly
and correctly initialize such an object?  And how can the constructor
for the static variable c, which will be run eventually [after
C_init::C_init() but before main()], ensure that it does not undo that
initialization?

I think I have a good answer to the second question: c should be
constructed using a copy constructor, with itself as argument:

 C c(c);

If the copy constructor is written correctly, this will be a no-op.
Writing a special "no-op" constructor

 C::C(/* some signature that is otherwise unused*/ ) {}

may not succeed, because C may inherit from classes with default
constructors that do real work.  I am assuming, by the way, that we don't
know the internals of the class B from which C is derived.

I am less satisfied with my answer to what C_init::C_init() should do to
initialize a "raw" object.  Here it is: all that we know about the raw
object is that it is a chunk of memory.  So all we can do is fill it with
bits.  These bits should be those of an object just like it:

 bcopy(&c2, &c, sizeof(C));

What kind of variable is c2?  It can't be global, because then we'd be back
where we started.  If it were automatic, hence local to C_init::C_init(),
its destructor would be called on exit from the function, and we don't want
that to happen since we may have violated some invariant by doing a raw
copy.  E.g. we may have copied a pointer, and we don't want that pointer to
be deleted.  So c2 must be allocated off the heap, and never freed.

So as far as I can see, the only reasonable choice for the body of
C_init::C_init() is

 if (count++ == 0) {
   C* c2 = new C(/* ... */);
   bcopy(c2, &c, sizeof(C));
 }

I'd like to know if that's right, or if there's some cleaner approach I've
missed.

 -Jonathan Amsterdam




Author: daniels@NeoSoft.com (Brad Daniels)
Date: Thu, 2 Sep 1993 14:30:54 GMT
Raw View
[ Apologies if this is a duplicate: my news machine is behaving oddly. }

In article <JBA.93Sep1132927@cfdev1138x.com>,
Jonathan Amsterdam <jba@lehman.com> wrote:
>---------------------------------------------------------------------------
>// file C.H
>
>class C: public B {
>public:
>  C();
>  // ...
>};
>
>extern C c;
>
>class C_init {
>  static int count;
>public:
>  C_init();
>};
>
>static C_init c_init;
[ Goes on to say that c gets constructed in the .cc file defining c
  before c_init gets constructed. ]

I suspect the problem is that the extern C c declaration is leaving the
compiler confused as to the order of definition.  When the variable gets
defined, it's marking the sequence as where it was declared.  Try moving
the extern C c declaration to after the static C_init c_init definition,
and you should be OK.  In general, I try to make the initialization
object definition the first thing in the header file.

This whole situtation is my biggest pet peeve about C++.  Although the
text of the language definition implies that there must be some ordering
to constructors (it says that constructors will be called for all objects
in a compilation unit before any object or function in that module is accessed
[from outside the compilation unit, presumably]), the universal interpretation
seems to be that there is no ordering imposed, since some later text mentions
that there is no *other* ordering, and the existing implementations take the
approach of "no ordering."

As things stand, it is impossible to safely declare static variables of
complex types.  The best you can do is declare a pointer to a complex pointer
and initialize it using a sleazy initialization counter.  Static variables
with constructors should either be removed from the language, or supported
properly by imposing cross-compilation-unit constructor ordering.  The problem
of cross-compilation-unit ordering is not even horrendously hairy.  I had
hoped to be able to submit a paper outlining a sample algorithm in time for
the next committee meeting, but unfortunately, work deadlines have prevented
me from having time to write it up.  I can only hope that the language con-
cerning initialization ordering does not get weakened, so that I will be able
to simply discuss a method of implementing something which the working
document already claims to require, rather than having to push a language
extension.

- Brad
--
Brad Daniels   |  "Let others praise ancient times.
daniels@neosoft.com  |   I am glad I was born in these."
I don't work for NeoSoft, and | - Ovid (43 B.C. - 17 A.D)
don't speak for my employer. |