If we choose to replace a class description name such as Point by a call to the ini- tialization function Point() to generate the class descriptions automatically, we have to modify each use of a class description and we need to tweak the ooc reports to generate slightly different files.
Class description names are used in calls to new(), cast(), isA(), isOf(), and in superclass selector calls. Using functions in place of pointer variables is a new con- vention, i.e., we will have to modify the application programs and the implementa- tion files. A goodANSI-C compiler (or the −pedantic option ofGNU-C) can be quite helpful: it should flag all attempts to pass a function name to a void * parameter, i.e., it should flag all those points in our C code where we have missed adding an empty argument list to a class name.
Changing the reports is a bit more difficult. It helps to look in the generated files for references to class descriptions. The representation file Point.r remains unchanged. The interface file Point.h declares the class and metaclass description pointers. It is changed from
____________________________________________________________________________________________ * The first const indicates that the result of the function points to a constant value. Only the second
108
___________________________________________________________________________9 Static Construction — Self-Organization
extern const void * Point; extern const void * PointClass;
to
extern const void * const Point (void); extern const void * const PointClass (void);
where the boldfaced const can only be used withGNU-C. It helps to have a portable report so we change the relevant lines in h.rep as follows
extern const void * `%const `class (void); `n `n extern const void * `%const `meta (void); `n `n
and we add a new report to the common file header.rep:
% const // GNUC supports const functions `{if `GNUC 1 const `}
ooc normally defines the symbolGNUCwith value zero but by specifying
$ ooc —DGNUC=1 ...
we can set this symbol to 1 on the command line and generate better code.
The implementation file Point.c contains many changes. All calls to cast() are changed; for the most part they are produced by the %casts request to ooc and thus by the casts and checks reports shown in section 8.4. Other calls to cast() are used in some selectors and superclass selectors and in the metaclass construc- tors, but these are generated by reports in etc.rep, c.rep, and c-R.rep . It now pays off that we have used ooc to enforce our coding standard — the standard is easy to change in a single place.
The significant change is, of course, the new style of initialization functions. Fortunately, these are also generated in c.rep and we derive the new versions by converting Point() as shown in the preceding section to report format in c.rep. Finally, we produce default functions such as
const void * const Object (void) { return & _Object;
}
by the init report in c-R.rep so that it can benefit from the GNUC conditional for ooc. This is a bit touchy because, as stated in section 7.5, the static initialization of _Object must be coded in Object.dc:
extern const struct Class _Object; extern const struct Class _Class; %init
static const struct Class _Object = { { MAGIC, & _Class },
"Object", & _Object, sizeof(struct Object),
Object_ctor, Object_dtor, Object_differ, Object_puto };
extern introduces forward references to the descriptions. %init generates the functions which reference the descriptions as shown above. static, finally, gives
internal linkage to the initialized descriptions, i.e., they are still hidden inside the implementation file Object.c.
As an exception, _Object must be the name of the structure itself and not a pointer to it so that & _Object can be used to initialize the structure. If we do not introduce a macro such as Class(), this makes little difference, but it does compli- cate munch a bit:
NF != 3 || $1 !˜ /ˆ[0—9a—f]+$/ { next } $2 ˜ /ˆ[bs]$/ { bsd[$3] = 1; next } $2 == "d" { sysv[$3] = 1; next } $2 == "T" { T[$3] = 1; next } END { for (name in T)
if ("_" name in bsd) # eliminate leading _ names[n ++] = substr(name, 2)
else if ("_" name in sysv) names[n ++] = name for (i = 0; i < n; ++ i)
printf "extern const void * %s (void);\n", names[i] print "\nconst void * (* classes [])(void) = {"
for (i = 0; i < n; ++ i) printf "\t%s,\n", names[i] print "0 };"
}
A class name should now occur as a global function and with a leading underscore as a local data item. Berkeley-nm flags initialized local data with s and uninitialized data with b, System-V-nm uses d in both cases. We simply collect all interesting symbols in three arrays and match them in the END clause to produce the array names[] which we actually need. There is even an advantage to this architecture: we can insert a simple shellsort [K&R88] to produce the class names in alphabetical order:
for (gap = int(n/2); gap > 0; gap = int(gap/2)) for (i = gap; i < n; ++ i)
for (j = i—gap; j >= 0 && \
names[j] > names[j+gap]; j —= gap) { name = names[j]
names[j] = names[j+gap] names[j+gap] = name }
If we use function calls in place of class names we do not need munch; however, a list of the classes in a program may come in handy for some other purpose.
9.5 Summary
Static objects such as class descriptions would normally be initialized at compile time. If we need constructor calls, we wrap them into functions without parame- ters and make sure that these functions are called early enough and in the proper
110
___________________________________________________________________________9 Static Construction — Self-Organization order. In order to avoid trivial but hard to diagnose errors, we should provide a mechanism which performs these function calls automatically — our programs should be self-organizing.
One solution is to use a linking technique, for example with the aid of a pro- gram such as munch, to produce an array with the addresses of all initialization functions and call each array element at the beginning of a main program. A func- tion main() with a loop executing the array can be part of our project library, and each program starts with a function mainprog() which is called by main().
Another solution is to let an initialization function return the initialized object. If the function is locked so that it does the actual work only once we can replace each reference to a static object by a call to its initialization function. Alternatively, we can use macros to produce the same effect more efficiently. Either way we can no longer take the address of a reference to a static object, but because the reference itself is a pointer value, this should hardly be necessary.
9.6 Exercises
The Class() macro is a more efficient, portable solution for automatic initialization of class descriptions than using functions. It is implemented by changing reports, class definitions, and application programs just as described above.
munch may have to be ported to a new system. If it is used together with the Class() macro, for a production system we can remove the conditional from the macro and initialize all class descriptions with munch. How do we initialize things in the right order? Can ooc be used to help here (consult the manual in appendix C about option−M for occ)? What about cast() in a production system?
All class descriptions should first show up in calls to cast(). We can define a fake class
typedef const void * (* initializer) (void); % Class ClassInit: Object {
initializer init; %}
and use statically initialized instances as ‘‘uninitialized’’ class descriptions:
static struct ClassInit _Point = {
{ MAGIC, 0 }, /* Object without class description */ initPoint /* initialization function */
};
const void * Point = & _Point;
cast() can now discover a class description with a null class description pointer, assume that it is a struct ClassInit, and call the initialization function. While this solution reduces the number of unnecessary function calls, how does it influence the use of cast()?