In other cases, an error is detected by the hardware or operating system rather than a library routine you called. A "signal" is sent to the process (your program) in these cases. Every process has a list of actions to be taken upon receipt of various signals. For most signals, the action is to print an error message to the standard error stream, dump core, and abort the process. You will, almost certainly, be plagued by this behavior before this class is through.
You may, in your program, redefine the actions which are to be taken
upon receipt of these signals.
You are expected to use this facility to have
your program recover gracefully from illegal arithmetic errors
(such as overflow).
To do this, first include the file
floatexception( sig, code ) int sig, code; { /* your code for exception handling */ }
Then the command: signal( SIGFPE, floatexception ); will tell the system to execute the procedure floatexception whenever the signal SIGFPE (Floating Point Exception) is received. After completion of the floatexception procedure (if it completes at all; more on this later), the code that caused the signal is continued as if nothing happened. The two parameters passed to floatexception by the system tell it what signal caused it to be executed (in case you have more than one signal set to call it) and a further integer code giving more details in some cases. See the manual page on signal for more details.
Note that, since your procedures are supposed to be self-contained, before your procedure quits it must reset any signals back to whatever they were before your procedure changed them. The signal function returns a pointer to whatever function was the old action to be taken. You must remember this pointer, and use it to reset the signal before quitting. In the case of a recursive procedure, you should probably use a separate top-level "bootstrap" procedure that the user will call, which just sets up the signals, allocates whatever memory will be needed, then calls the recursive procedure. This top-level procedure will then return memory and reset signals before returning control to its caller. A simplified outline of such a top-level procedure follows:
/* define possible returns of the procedure */ typedef enum { FAILSIG, FAILMEM, FAILFLOAT, FAILRESETIG, SUCCEED }; void (*old_sigfpe)(); /* to remember the old action associated with SIGFPE */ if ( -1 == (int) ( old_sigfpe = signal( SIGFPE, floatexception ) ) ); /* something wrong with the signal system: bail out */ return( FAILSIG ); . . /* allocate memory, call recursive procedure, free memory */ . if ( -1 == (int) signal( SIGFPE, old_sigfpe ) ) /* signal reset failed, but we have otherwise succeeded */ return( FAILRESETSIG ); return( SUCCEED );
The typedef should probably be in an include file, so that your calling program (the tester) can decipher the return status of your procedure.
The other main problem with interrupt handling is how to abort a procedure when it may be several levels deep in recursion. One technique is to set up a global flag which the procedure checks periodically and exits if the flag is changed. In this case, floatexception would be the one to change the flag. This is not great, since the procedure must waste time checking the flag.
A better solution is provided by the C library functions
setjump and longjmp.
To use them, include the file
if ( x == setjmp( env ) ) { /* code to deal with error x: (probably just clean up and */ /* return an error status to caller) */ } else { /* code to continue with the task at hand */ }
In this case, the code in floatexception should just be:
longjmp( env, /* some non-zero integer of your choosing */ );