1.14k likes | 1.35k Views
Topics on Exceptions & Assertions. Asyncronous Exceptions Using assert.h and signal.h User Defined Exceptions C++ Exceptions Throwing Exceptions Try Blocks Handlers Exception Specification terminate() and unexpected() The Philosophy Of Error Recovery. Error Recovery. Exceptions.
E N D
Topics on Exceptions & Assertions • Asyncronous Exceptions • Using assert.h and signal.h • User Defined Exceptions • C++ Exceptions • Throwing Exceptions • Try Blocks • Handlers • Exception Specification • terminate() and unexpected() • The Philosophy Of Error Recovery
Exceptions • Exceptions generally are unexpected error conditions • floating point divide by zero • out of memory • Normally these conditions terminate the user program with a system-provided error message • C++ gives the programmer the opportunity to recover from these conditions and continue program execution
Can’t we just assume that the functions we write will work will all the data we get? No, we need to put in some assertions. Program Correctness
Program Correctness and Assertions • Program correctness: computation terminated with correct output dependent on correct input • Invoker of computation had the responsibility of providing correct input as a precondition • Successful computation satisfied postcondition • Intention of providing a fully formal proof of correctness is an idealization not usually done
Programmer Discipline and Assertions • The discipline of thinking out appropriate assertions frequently causes the programmer to avoid bugs and pitfalls • Assertions can be monitored at run-time to provide very useful diagnostics • The C and C++ communities increasingly emphasize the use of assertions
The assert Macro • The standard library assert.h provides the macro • void assert(int expression); • If the expression evaluates as false, then execution is aborted with diagnostic output • The assertions are discarded if the macro NDEBUG is defined
Preconditions and Postconditions • Precondition • assert (grade > = 0); • student.score = grade; • Postcondition • minmax (data, size, min, max); • assert (min <= max);
Assertions and Contracts • Assertions provide guarantees the code will work correctly • The client or user of code guarantees that the inputs to the code are correct • The manufacturer or provider of code guarantees that the outputs from the code are correct
vect Without the assert Macro • vect::vect(int n) • { • if (n <= 0) { • cerr << "illegal vect size " << n << endl; • exit(1); • } • size = n; • p = new int[size]; //assert (p != 0); • }
vect With the assert Macro • vect::vect(int n) • { • assert (n > 0); //contractual precondition • size = n; • p = new int[size]; • assert (p != 0); //contractual postcondition • } • Assertions replace ad hoc use of conditional tests with a more uniform methodology • This is conducive to better practice • The down side is that the assertion methodology does not allow a retry or other repair strategy to continue program execution
Sophisticated Approach using checks.h • Different testing levels may be used, as found in the Borland checks.h file flag _DEBUG Testing Level _DEBUG 0 no testing _DEBUG 1 PRECONDITION tests only _DEBUG 2 CHECK tests also
Using Testing Levels • Once the library functions are thought to be correct, the level of checking is reduced to only testing preconditions • Once the client code is debugged, all testing can be suspended
Placing Assertions in Bubble Sort (1 of 3) • Bubble sort does not work correctly • Place assertions in this code to test that the code is working properly • // Title: bubble • #include <iostream.h> • void swap(int a, int b) • { • int temp = a; • a = b; • b = temp; • }
Placing Assertions in Bubble Sort (2 of 3) • void bubble(int a[], int size) • { • int i, j; • for (i = 0; i != size; ++i) • for (j = i ; j != size; ++j) • if (a[j] < a [j + 1]) • swap (a[j], a[j + 1]); • };
Placing Assertions in Bubble Sort (3 of 3) • main() • { • int t[10] = { 9, 4, 6, 4, 5, 9, —3, 1, 0, 12}; • bubble(t, 10); • for (int i = 0; i < 10; ++i) • cout << t[i] << '\t'; • cout << "\nsorted? " << endl; • }
Assertions to Detect Failure • Postcondition that will fail in general • c = a - b; • swap (a, b); • assert (-c == (a - b) );
I put in an assertion to make sure that the swap function does what I intended. Assertions in Bubble Sorting • if (a[j] < a[j + 1] { • swap (a[j], a[j + 1]); • assert (a[j] >= a[j + 1]); • }
Assertions in main() • for (int i = 0; i < 10; ++i) { • cout << t[i] << '\t'; • assert (i == 9 || t[i] >= t[i + 1]); • // short circuit t[9] > t[10] not evaluated • }
Assertions and Classes • Member functions are guaranteed to work on correct data and input • If a client owns a stack s, then push works if stack is not full and if input is correct • Precondition guaranteed by client • s.full() is false • data is legal • Postcondition guaranteed by manufacturer • s.top is data
The signal.h Functions • The ANSI C package signal.h handles asynchronous hardware exceptions • These exceptions are system-dependent and usually are raised by hardware detection of a special condition • The signal.h file also handles synchronous exceptions using the raise() function • While not as sophisticated as C++ language exception handling, signal.h has a long history as a useful library
System-defined Exceptions & signal.h • The signal.h file provides a standard mechanism for handling system-defined exceptions in a straightforward manner • Exceptions are defined within this library and are system-dependent integer values • #define SIGINT 2 /*interrupt signal */ • #define SIGFPE 8 /*floating point exception */ • #define SIGABRT 22 /*abort signal */ • The system can raise these exceptions
Raising Exceptions • Hitting control-C on the keyboard, on many systems, generates an interrupt, usually killing the current user process • The raise() function, prototyped in signal.h, can be used to generate an explicit exception • //floating point exception signal raised • raise(SIGFPE);
Let’s change the way the system handles floating point errors. Overriding the System Error Handling
Installing the Handler using signal • Exceptions can be handled by use of the signal() function • It associates a handler function with a signal • It can be used to ignore the signal or to reinstall the default action • Invoking signal() is called installing the handler and it replaces the normal system action with the user-defined handler
Using signal • Call my_abrt() if SIGABRT is raised • signal(SIGABRT, my_abort); • Default action if SIGABRT is raised • signal(SIGABRT, SIG_DFL); • Ignore SIGFPE • signal(SIGFPE, SIG_IGN);
Keyboard Termination (1 of 4) • A loop interrupted from the keyboard • Upon interruption, the handler requests from the user whether or not the program should continue executing • // Title: interrupt • //Interrupts handled using signal.h • #include <signal.h> • #include <time.h> • #include <iostream.h> • #include <stdlib.h> • #define CPS CLOCKS_PER_SEC
Requiring Keyboard Input The screen saver program only terminates when you hit a key
Keyboard Termination (2 of 4) • void cntrl_c_handler(int sig); • main() • { • int i = 0, j; • cout << "COUNT TO J MILLION, Enter j:"; • cin >> j; • j *= 1000000; • signal(SIGINT, cntrl_c_handler); • cout << (double)clock()/CPS << " start time\n";
Keyboard Termination (3 of 4) • while (1) { • ++i; • if (i > j) { • cout <<(double)clock()/CPS << " end loop\n"; • cout << " HIT " << j/1000000 << " MILLION" • << endl; • raise(SIGINT); • cout << "\nEnter j: "; • cin >> j; • j *= 1000000; i = 0; • cout << (double)clock()/CPS << " start loop\n"; • } • } • }
Keyboard Termination (4 of 4) • void cntrl_c_handler(int sig) • { • char c; • cout << "INTERRUPT"; • cout << "\ntype y to continue: "; • cin >> c; • if (c == 'y') • signal(SIGINT, cntrl_c_handler); • else • exit(0); • }
Comments on handler Program (1 of 2) • The handler cntrl_c_handler function is associated with the interrupt SIGINT • On detecting the next interrupt, system invokes cntrl_c_handler() instead of default action • The function clock() is found in time.h and it reads out elapsed cpu cycles in local units • Divided by the integer constant CPS, it gives times in seconds for executing the computation • Interrupt signal raised by explicit raise(SIGINT) • Implicitly cntrl_c_handler() is invoked
Comments on handler Program (2 of 2) • User-defined cntrl_c_handler handles a SIGINT exception is invoked when on interrupt to ask the user whether the program is to be continued • On the request to continue execution, the exception handler is reinstalled • Without the handler being reinstalled, system reverts to its default handling of the interrupt • exit() function in stdlib.h is invoked to terminate execution if the user selects not to continue
A Modified Handler • void cntrl_c_handler(int sig) • { • char c; • static int count = 0; • cout << "INTERRUPT"; • cout << "\ntype y to continue:"; • cin >> c; • if (c == 'y' && count++ < N) • signal(SIGINT, cntrl_c_handler); • else • abort(); • } • The variable N is an integer constant
The setjmp.h Function (1 of 2) • On ANSI systems the file setjmp.h provides declarations that allow nonlocal resumption of processing • typedef long jmp_buf[16]; • space to holdenvironment • int setjmp(jmp_buf env); • save callingenvironment • void longjmp(jmp_buf env, int v); • restoreenvironment
The setjmp.h Function (2 of 2) • We could rewrite the code in the interrupt program to return to the beginning of the timing loop anytime an interrupt is caught • Using setjmp.h bypasses a normal block exit and can be dangerous because objects can be left undestroyed
If the size of the vect is negative or 0, or it there isn’t enough memory, I’ll raise an exception Exceptions and vect
Handling vect Exceptions (1 of 3) • const int SIGHEAP = SIGUSR1; • const int SIGSIZE = SIGUSR2; • vect::vect(int n) • { • if (n <= 0) { • raise(SIGSIZE); • cout << "\nEnter vect size n: "; • cin >> n; //retry with user provided value • } • p = new int[n]; • if (p == 0) { • raise(SIGHEAP); • p = new int[n]; • } • }
Handling vect Exceptions (2 of 3) • void vect_size_handler(int sig) • { • char c; • cout << "\nSIZE ERROR\nENTER y to continue" • << " with default: "; • cin >> c; • if (c == 'y') { • signal(SIGSIZE, vect_size_handler); • } • else • exit(0); • }
Handling vect Exceptions (3 of 3) • void vect_heap_handler(int sig) • { • //Possible action to reclaim store for heap • //or exit gracefully • . . . • }
Replacing Assertions with Handlers • Replace the assertions with conditions that raise user-defined signals then define handlers that can provide appropriate action • Contrast this complicated technique with the standard technique for handling heap exhaustion using the set_new_handler()
C++ Exceptions • C++ introduces an exception handling mechanism that is sensitive to context • It can be more informed than a signal.h handler and can provide more sophisticated recovery • It is not intended to handle the asynchronous exceptions defined in signal.h
C++ Exceptions with try, catch, throw • try { //try block • . . . • a[i] = a[j] = a[k]; • . . . //relevant handlers follow • } • catch (int index) { ... } • catch (char* message) { ... } • int& vect::operator[](int i) • { • if (i < 0 || i >= size) • throw (i); //throwing an exception • else • return (p[i]); • }
The try Block • Context for raising an exception is a try block • Handlers are declared at the end of a try block using the keyword catch • C++ code is allowed to directly raise an exception in a try block by using the throw expression • Exception is handled by invoking appropriate handler selected from a list of handlers found immediately after handler's try block
Modifying vect for try and throw (1 of 2) • vect::vect(int n) • { • if (n < 1) • throw (n); • p = new int[n]; • if (p == 0) • throw ("FREE STORE EXHAUSTED"); • }
Modifying vect for try and throw (2 of 2) • void g() • { • try { • vect a(n), b(n); • . . . • } • catch (int n) { . . .} //catch incorrect size • catch (char* error) { . . .} //catch no-memory • }
Comments on the vect Exception Handling • The first throw() has an integer argument and matches the catch(int n) signature • When an incorrect array size has been passed as an argument to the constructor, this handler is expected to perform an appropriate action, for example, an error message and abort • The second throw() has a pointer to character argument and matches the catch(char* error) signature
Throwing Exceptions (1 of 2) • Syntactically throw expressions come in two forms • throw • throw expression • The throw expression raises an exception • The innermost try block in which an exception is raised is used to select the catch statement that processes the exception
Throwing Exceptions (2 of 2) • The throw expression with no argument rethrows the current exception and is typically used when a second handler called from the first handler is needed to further process the exception • Expression thrown is a static, temporary object that persists until exception handling is exited • The expression is caught by a handler that may use this value