Class descriptions essentially are static objects. They ought to exist as long as the
main program is active. This is normally accomplished by creating such objects as
global orstaticvariables and initializing them at compile time.
Our problem is that we need to call
Class_ctor()
and the other metaclass con-
structors to hide the details of inheritance when initializing a class description.
Function calls, however, can only happen at execution time.
The problem is known asstatic constructor calls— objects with a lifetime equal
to the main program must be constructed as soon asmain()is executed. There is
no difference between generating static and dynamic objects.
initPoint()and simi-
lar functions simplify the calling conventions and permit calls in any order, but the
actual work is in either case done bynew()and the constructors.
At first glance, the solution should be quite trivial. If we assume that every
class description linked into a program is really used we need to call every
init-
function at the beginning of
main(). Unfortunately, however, this is not just a
source text processing problem.
ooccannot help here because it does not know —
intentionally — how classes are put together for a program. Checking the source
code does not help because the linker might fetch classes from libraries.
Modern linkers such as
GNUld
permit a compiler to compose an array of
addresses where each object module can contribute elements as it is linked into a
program. In our case we could collect the addresses of allinit-funtions in such an
array and modifymain()to call each function in turn. However, this feature is only
available to compiler makers, not to compiler users.
Nevertheless, we should take the hint. We define an array
initializers[]
and
arrange things inmain()as follows:
void (* initializers [])(void) = { 0 };
int main ()
{ extern void (* initializers [])(void); void (** init)(void) = initializers; while (* init)
(** init ++)(); ...
All that remains is to specify every initialization function of our program as an ele-
ment ofinitializers[]. If there is a utility likenmwhich can print the symbol table of
a linked program we can use the following approach to generate the array automati-
cally:
$ cc —o task object... libooc.a
$ nm —p task | munch > initializers.c
$ cc —o task object... initializers.c libooc.a
We assume thatlibooc.a
is a library with a module
initializers.o
which defines the
array
initializers[]
as shown above containing only the trailing null pointer. The
library module is only used by the linker if the array has not been defined in a
module precedinglibooc.aon the command line invoking the compiler.
nm
prints the symbol table of the
task
resulting from the first compilation.
munchis a small program generating a new moduleinitializers.cwhich references
all
init-functions in
task. In the second compilation the linker uses this module
rather than the default module fromlibooc.a
to define the appropriateinitializers[]
fortask.
Rather than an array,
munch
could generate a function calling all initialization
functions. However, as we shall see in chapter 12, specifically a list of classes can
be put to other uses than just initialization.
The output fromnmgenerally depends on the brand of
UNIXused. Luckily, the
option−pinstructs Berkeley-nmto print in symbol table order and System-V-nmto
produce a terse output format which happens to look almost like the output from
Berkeley-nm. Here ismunchfor both, implemented usingawk:
NF != 3 || $2 != "T" || $1 !˜ /ˆ[0—9a—fA—F]+$/ { next } $3 ˜ /ˆ_?init[A —Z][A—Za—z]+$/ { sub(/ˆ_/, "", $3) names[$3] = 1 } END { for (n in names)
printf "extern void %s (void);\n", n print "\nvoid (* initializers [])(void) = {" for (n in names)
printf "\t%s,\n", n print "0 };"
}
The first condition quickly rejects all symbol table entries except for those such as
00003ea8 T _initPointAssuming that a name beginning with
init
and a capital letter followed only by
letters refers to an initialization function, an optional initial underscore is stripped
(some compilers produce it, others do not) and the rest is saved as index of an
array
names[]. Once all names have been found,
munch
generates function
declarations and definesinitializers[].
106
___________________________________________________________________________
9 Static Construction — Self-OrganizationThe arraynames[]is used because each name must be emitted twice. Names
are stored as indices rather than element values to avoid duplication.*
munch
can
even be used to generate the default module for the library:
$ munch < /dev/null > initializers.c