richw@ada-uts.UUCP (08/12/85)
EXCEPTION HANDLING ??!? The following includes "files" of macros and a simple modification of the "exit" routine which allows one to do pseudo-exception-handling in C. An example of their use is provided in the "test.c" file. There are problems with these macros (and the modified "exit"), I admit. The purpose of this note is to get feedback on possible solutions to some of the problems. I'd also like INTELLIGENT comments about what's going on (please refrain from verborrhea like "How could you name the macro 'except_when'?! Name it 'if_exception'!" ). Problems I can see right off: 1) The raise macro must still use a return statement (which is why I've provided raise_return, for those cases where garbage results are inappropriate). 2) Arbitrary statements may be executed between the call to the procedure that raised an exception and the handler. 3) Unhandled exceptions come to the attention of the user only when another "raise" is attempted or when the program terminates. 4) The raise macro includes a return statement since most exceptions (at least in programs that I write) are used to signal alternate termination conditions of a procedure. This implies that "local" exceptions can't be raised (e.g. to jump out of nested loops). 5) An "exception" is simply a string. It's "declaration" requires (?) some redundancy (i.e. the variable name and the string literal will usually look the same). -- Rich Wagner ------------------------- test.c ------------------------------- #include "exceptions.h" #include <stdio.h> Exception overflow = "Overflow"; raiser(b) int b; { fprintf(stderr, "Raiser called with %d\n", b); if (b) raise(overflow); fprintf(stderr, "Leaving raiser\n\n"); } main() { raiser(0); except_when(overflow) fprintf(stderr, "Caught when it wasn't raised\n"); end_except; raiser(1); except_when(overflow) fprintf(stderr, "Caught\n"); end_except; raiser(0); raiser(1); raiser(1); fprintf(stderr, "Last line of main\n"); } ------------------------ test output ---------------------------- Raiser called with 0 Leaving raiser Raiser called with 1 Caught Raiser called with 0 Leaving raiser Raiser called with 1 Raiser called with 1 Unhandled exception: Overflow ------------------------ exceptions.h --------------------------- extern int _xxraised; extern char *_xxexc; #define raise(e) { if (_xxraised) exit(1);\ _xxexc = e; _xxraised = 1; return; } #define raise_return(e,r) { if (_xxraised) exit(1);\ _xxexc = e; _xxraised = 1 ; return r; } #define except_when(e) if (_xxraised) {\ if (_xxexc == (e)) { _xxraised = 0; #define when(e) } else if (_xxexc == (e)) { _xxraised = 0; #define end_except } else exit(1); } typedef char *Exception; ------------------------ exceptions.c ----------------------------- #include <stdio.h> int _xxraised = 0; char *_xxexc; exit(status) int status; { if (_xxraised) { fprintf(stderr, "Unhandled exception: %s\n", _xxexc); _xxexit(status ? status : 1); } _xxexit(status); /* _xxexit is the old "exit" we all know and love, */ /* just renamed. */ }
robert@cheviot.uucp (Robert Stroud) (08/19/85)
Rich Wagner (richw@ada-uts.UUCP) posted some macros for exception handling and asked for comments and suggestions. I can recommend an article in Software Practice & Experience by Peter Lee a few years ago (1983, pp 389-405) which describes a series of macros and routines which add exception handling in the style of Ada or Clu to C. Basically they work by hiding a stack of setjmp/longjmp contexts inside a macro skeleton of BEGIN ... EXCEPT WHEN(exception1) ... WHEN(exception2) ... OTHERS ... END I can post the code from the article if there is enough interest. Robert Stroud, Computing Laboratory, University of Newcastle upon Tyne. UUCP ...!ukc!cheviot!robert ARPA robert%cheviot.newcastle@ucl-cs.ARPA
richw@ada-uts.UUCP (08/22/85)
I'd like to confirm the obvious: Yes, I'm VERY interested in having the code from the paper suggested by Robert Stroud posted (having posted the original note). As a reminder, that paper is in the 1983 Software Practice & Engineering (pp.389-405), and is by Peter Lee. I'd also like to mention the following paper -- I received some personal mail suggesting it: "An Exception Handler for C" by Allman and Been in the Portland (Summer 85) Usenix proceedings (Note that the librarian where I work is having trouble tracking this paper down, so...) -- Rich Wagner P.S. Robert Stroud also name-dropped Clu -- is net.lang.c actually starting to show a little class ?? :-) P.P.S. Probably not. If it had class, it'd "talk a little" about Smalltalk ("show a little cluster ?")
richw@ada-uts.UUCP (08/24/85)
I think I've figured out how to improve upon my first try (the original note) based on Robert Stroud's mentioning a stack for setjmp/longjmp environments. ALL of the problems (except for the fairly trivial #5) which I mentioned in the first note seem to be taken care of now. I also fixed a problem not realized originally (i.e. why a stack is needed instead of a single environment...) IN ANY CASE, my new test files (test.c and add.c) and the files which encapsulate the exception handling mechanism (exceptions.{c,h}) follow. I'm not claiming the mechanism is bug-free!! I still haven't read (or found) the papers suggested, so I'm still kinda "wingin'-it"... I DO know that THIS test case seems to work. Again, I'd love comments... -- Rich Wagner P.S. Because I use a funny terminal, I can't directly type left and right-square-brackets. In the following code, these are represented by [ and ], respectively. P.P.S. Many thanks to Robert Stroud !! ------------------------------------------------------------------------- /********* exceptions.h *********/ #include <setjmp.h> #include <stdio.h> typedef char *Exception; extern jmp_buf _env_stack[]; extern int _env_index, _bad_index; extern Exception _exc; #define Begin { if (_env_index == _bad_index) {\ fflush(stdout);\ fprintf(stderr, "Handlers nested to deeply\n");\ exit(1);}\ if (!setjmp(_env_stack[_env_index++])) { #define Except _env_index--; #define When(e) } else if (_exc == e) {\ _env_index--; #define Others } else if (1) {\ _env_index--; #define End } else {\ fflush(stdout);\ fprintf(stderr,"Unhandled Exception: %s\n",_exc);\ exit(1);\ } } #define raise(e) {_exc = e; longjmp(_env_stack[_env_index-1],1); } ------------------------------------------------------------------------- /********* exceptions.c *********/ #include <setjmp.h> #define STACK_SIZE 50 jmp_buf _env_stack[STACK_SIZE]; int _env_index = 0; int _bad_index = STACK_SIZE; char *_exc; ------------------------------------------------------------------------- /********* test.c *********/ #include <stdio.h> #include "exceptions.h" extern Exception overflow; extern Exception underflow; main() { int x; Begin fprintf(stderr, "Calling add(4,1)\n"); x = add(4,1); fprintf(stderr, "After call to add(4,1)\n"); Except When(overflow) fprintf(stderr, "Overflow caught but not expected\n"); End; Begin fprintf(stderr, "Calling add(4,7) -- expect overflow\n"); x = add(4,7); fprintf(stderr, "After call to add(4,7)\n"); fprintf(stderr, "Expected exception not raised"); Except When(overflow) fprintf(stderr, "Overflow caught\n"); End; Begin fprintf(stderr, "Calling add(-4,-7) -- expect underflow\n"); x = add(-4,-7); fprintf(stderr, "After call to add(-4,-7)\n"); fprintf(stderr, "Expected exception not raised"); Except When(overflow) fprintf(stderr, "Overflow caught but not expected\n"); End; fprintf(stderr, "Last line of main\n"); } ------------------------------------------------------------------------- /********* add.c *********/ #include "exceptions.h" Exception overflow = "Overflow"; Exception underflow = "Underflow"; static my_add(a, b) int a, b; { a += b; if (a >= 10) raise(overflow); if (a <= -10) raise(underflow); return (a); } add(a, b) int a, b; { Begin return my_add(a,b); Except When(overflow) raise(overflow); When(underflow) raise(underflow); End; }
richw@ada-uts.UUCP (08/26/85)
Sorry if this is getting boring, but... I fixed a bug; a "raise" occuring before a "Begin" is encountered works as one would expect now. I also made things a little more space efficient. Otherwise, the macros' behavior hasn't changed. Also, note that the environment stack has a fixed depth -- this can be changed (at the expense of more procedure calls) by having pushed environments be allocated from the heap (i.e. use malloc). The updated exceptions.{c,h} follow: ----------------------- exceptions.h ---------------------------- #include <setjmp.h> typedef char *Exception; extern jmp_buf _env_stack[]; extern int _env_index, _bad_index; extern Exception _exc; #define Begin { if (_env_index == _bad_index) _env_overflow();\ if (!setjmp(_env_stack[_env_index++])) { #define Except _env_index--; #define When(e) } else if (_exc == e) { #define Others } else if (1) { #define End } else _unhandled(); } #define raise(e) (_exc = e,\ (_env_index == 0)\ ? _unhandled() :\ longjmp(_env_stack[--_env_index],1) ) --------------------------- exceptions.c ---------------------------- #include <setjmp.h> #include <stdio.h> #define BELL '\007' /* ASCII control-G */ #define STACK_SIZE 101 jmp_buf _env_stack[STACK_SIZE]; int _env_index = 0; int _bad_index = STACK_SIZE; char *_exc; _unhandled() { fflush(stdout); putc(BELL, stderr); fprintf(stderr, "\nUnhandled Exception: %s\n", _exc); exit(1); } _env_overflow() { fflush(stdout); putc(BELL, stderr); fprintf(stderr, "\nException handlers nested too deeply"); fprintf(stderr, "\nMaximum is %d\n", STACK_SIZE - 1); exit(1); }