Keeping executables small (was: main() and exit())
Steve Summit
scs at adam.pika.mit.edu
Sun Jan 8 04:29:43 AEST 1989
In article <8634 at bloom-beacon.MIT.EDU> I described a way, using
a global function pointer, to keep _cleanup and the rest of stdio
out of programs which don't need it, while still having exit
call _cleanup when appropriate.
In article <9295 at smoke.BRL.MIL> gwyn at brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
>Your technique is equivalent to use of atexit(), but specialized to
>support just STDIO. (It is more efficient, though...)
Doug is correct. In the case of exit/cleanup, the global
function pointer technique is embarrassingly close to atexit,
amounting to a special-cased implementation of the same concept,
which I would have mentioned in my article if I had realized it
in time. (Efficiency, run-time efficiency anyway, was hardly my
concern.)
The global function technique is quite useful, and its benefits
more obvious, in cases where a general function-call registering
mechanism (such as atexit) is neither available nor appropriate.
An example will illustrate.
In one of my (many) device-dependent libraries of Unix-style
plot subroutines, I have a function which draws dashed lines (the
hardware doesn't support them). The code is quite large (it
draws nice looking dashed lines under almost all circumstances),
but is only needed if the calling program calls linemod() (to
request a line style of other than the default "solid"). Yet the
dashed-line drawing code is called from the main line-drawing
routine, which is called by virtually all graphics programs, and
so would at first seem to be loaded whether needed or not.
In fact, line() calls _dashline() indirectly through a function
pointer:
int (*_dashlinefunc)() = NULL;
int _linestyle = 0;
line(x1, y1, x2, y2)
int x1, y1, x2, y2;
{
if(_linestyle != 0 && _dashlinefunc != NULL)
(*_dashlinefunc)(x1, y1, x2, y2);
else {
/* draw simple, solid line */
}
}
In a separate file (linemod.c):
extern int _linestyle;
extern int (*_dashlinefunc)();
extern int _dashline();
linemod(style)
char *style;
{
/* parse style string and set _linestyle accordingly */
_dashlinefunc = _dashline;
}
A program that never calls linemod() only draws solid lines and
needs neither linemod() nor _dashline(). If and when the program
calls linemod(), linemod.o is pulled out of the library (during
the next recompile and relink), which adds _dashline to the
undefined external list, which then (and only then) pulls in all
of the dashed-line machinery. (The code for _dashline could
equivalently be included in the same source file with linemod.)
In any case, _dashline is not directly an undefined external of
the normal line function, even though that is the only spot from
which it is called.
(The technique requires a mildly objectionable stylistic
concession, in that the defining instances of the global
variables _linestyle and _dashlinefunc cannot be in the file with
the rest of the dashed-line code, where it seems they belong.)
If the authors of large, general-purpose libraries would pay
attention to which routines are required by all callers (as
opposed to those needed only by programs using the more exotic
features), and then use a few little tricks to make the linker's
behavior more closely match the actual call pattern (i.e. include
only those modules expected to be actually called) we wouldn't
hear as many complaints about huge, bloated executables.
(In the case of exit and stdio, I suspect that the excuse for not
taking steps to make loading of most of stdio truly optional is
that the number of useful programs that don't use stdio is
small.)
In the case of other libraries, such as plotting packages, window
managers, or user interface subsystems, the potential for
substantial trimming of executables is significant. Associated
with most such libraries are horror stories about the incredible
resultant size of apparently simple programs (open a window and
print "Hello, world!" in it, for instance).
Steve Summit
scs at adam.pika.mit.edu
More information about the Comp.lang.c
mailing list