gnu@hoptoad.uucp (John Gilmore) (07/05/87)
[Cross posted to alt.sources because it is unmoderated; cross postings to both mod and unmod groups, or to two mod groups, do not work properly, so I couldn't send it to comp.sources.misc. This article contains an implementation of the draft C standard's atexit().] zemon@felix.UUCP (Art Zemon) wrote: > The original idea behind this was to keep the tools in > /usr/bin and /bin small by not including the stdio library. > Exit(), if it called _cleanup(), would bring in large chunks > of otherwise unused code. > > I think a better solution would be to rename the existing > exit() to _exit() and create an exit() subroutine which > calls _cleanup() and change the tools which don't use stdio > to call _exit(). Whew! I said all that in one breath. :-) While Art's solution works, there is a cleaner solution than requiring the programmer to call _exit if she doesn't use stdio. Minix can remain small and still be fixed to work properly. There is a library routine in SunOS called "on_exit()" which registers a function so that it will be called when exit() is called. The Sun manual pages claim it is specific to SunOS. However, an identical function called "atexit()" has appeared in the draft C language standard, where it is claimed to be descended from Whitesmiths C. At any rate, it's an idea whose time has come! This should be easy to implement; you make an array of 33 function pointers (draft C std requires 32 and we need one for stdio) and an index, say in <sys/atexit.h>: #define MAXATEXIT 32 void (*__exits[MAXATEXIT+1])(); short __exit_i; then rewrite exit.c: #include <sys/atexit.h> void exit() { while (__exit_i > 0) (*__exits[--__exit_i])(); /* Call them, LIFO */ _exit(); } In a separate file (atexit.c), so it doesn't get dragged in unless called, you do: #include <sys/atexit.h> void atexit(fun) void (*fun)(); { __exits[__exit_i++] = fun; } and then inside _flsbuf from stdio, do something like: ... { static flaggola = 1; void cleanup(); if (flaggola) { flaggola = 0; atexit(cleanup); } } ... (You have to make sure that _flsbuf will get called to output the first character, rather than sticking it in the buffer, but that's easy. Most stdio's do this anyway, so they can allocate the output buffer at runtime.) This ensures that if anything is in the stdio buffers, it will be flushed upon exit, with only a minor space penalty on applications that don't call stdio (33 function pointers, an int, and a few instructions in exit()). It also adds a useful feature from the draft C standard. Maybe for Minix 1.3? -- {dasys1,ncoast,well,sun,ihnp4}!hoptoad!gnu gnu@ingres.berkeley.edu Alt.all: the alternative radio of the Usenet. Contributions welcome - post 'em
fnf@mcdsun.UUCP (Fred Fish) (07/08/87)
In article <2368@hoptoad.uucp> gnu@hoptoad.uucp (John Gilmore) writes: >While Art's solution works, there is a cleaner solution than >requiring the programmer to call _exit if she doesn't use stdio. >Minix can remain small and still be fixed to work properly. > ... >This should be easy to implement; you make an array of 33 function Yes, I just recently put a very similar fix in our version of SVR3 here, and it was only about a 2 hour job, most of which was figuring out the correct place in stdio to trigger the cleanup from exit. The results were well worth the work, a null program "main(){}" went from over 14Kb linked to just 298 bytes. The original 14Kb was because crt0.o references exit(), which pulls in cleanup(), which pulls in ... One possible problem with the implementation John outlined: what if the user calls atexit() with his own functions before doing any stdio? I.E., if he calls: atexit(func1); atexit(func2); do some stdio stuff for the first time atexit(func3); The queue of functions will look like: func3 -> _cleanup -> func2 -> func1 which is dequeue'd and executed in the order func3() _cleanup() func2() func1() with possibly unintentional results. My solution was to not use the atexit() routine to register the cleanup function, just set a global pointer (_stdio_cleanup) in exit and insure that it got dereferenced *after* all the functions registered by atexit(). P.S. I put my pointer initialization as the first statement in flsbuf() in flsbuf.c: unsigned char c1; + extern VOID (*_stdio_cleanup)(); /* Found in exit() module */ + _stdio_cleanup = _cleanup; do { This may or may not be the theoretically correct place to put it, so if someone more knowledgeable about stdio wants to speak up... -Fred -- = Drug tests; just say *NO*! = Fred Fish Motorola Computer Division, 3013 S 52nd St, Tempe, Az 85282 USA = seismo!noao!mcdsun!fnf (602) 438-5976
mpl@sfsup.UUCP (M.P.Lindner) (07/10/87)
In article <333@mcdsun.UUCP>, fnf@mcdsun.UUCP writes: < In article <2368@hoptoad.uucp> gnu@hoptoad.uucp (John Gilmore) writes: < >While Art's solution works, there is a cleaner solution than [text deleted] < function, just set a global pointer (_stdio_cleanup) in exit and insure < that it got dereferenced *after* all the functions registered by atexit(). < < P.S. I put my pointer initialization as the first statement in flsbuf() < in flsbuf.c: < < unsigned char c1; < + extern VOID (*_stdio_cleanup)(); /* Found in exit() module */ < < + _stdio_cleanup = _cleanup; [more text deleted] < This may or may not be the theoretically correct place to put it, so if < someone more knowledgeable about stdio wants to speak up... [still MORE text deleted] < = Fred Fish Motorola Computer Division, 3013 S 52nd St, Tempe, Az 85282 USA > = seismo!noao!mcdsun!fnf (602) 438-5976 The correct thing to do theoretically is declare _stdio_cleanup to be a global in exit, and declare *and initialize* it in data.c (the file which declares _iob). That way, if there's no reference to stdio, data.c doesn't get loaded becasue there's no unresolved reference to _stdio_cleanup. However, if any stdio is used, data.c will get loaded (since _iob is referenced), and the loader will merge the two declarations (technically non-kosher, but laoders do these things) producing the desired result. As an exampl (and also a test) try the following: main.c: main() { foo(); } foo.c: int foobar; foo() { printf("%d\n", foobar); } bar.c: int xyzzy; int foobar = 3; cc -c foo.c bar.c ar rv libbar foo.o bar.o cc main.c libbar a.out should print "0" for the value of foobar as printed by foo(). now edit main.c: extern int xyzzy; main() { xyzzy = 0; foo(); } cc main.c libbar a.out should print "3" for the value of foobar, since the reference to xyzzy pulled in bar.o which initialized foobar to 3. Simple, no? The method of putting the statement in flsbuf has the disadvantage that if flsbuf is never called (ie no output buffer is filled by the program), the output doesn't get flushed. Thus, a program like printf("hello world") fails to produce output, while printf("hello world\n") works, because of line buffering. I hope I was able to explain this satisfactorily to the net audience. Mike Lindner ....!ihnp4!attunix!mpl