Exception Handling
I. Basic Concepts
A. Exception:
1. An unusual event, either erroneous or not
2. Detectable by either hardware or software
3. May require special processing
B. Exception handler: A code fragment that processes an exception
C. Examples of exceptions
1. End of file
2. Division by 0
3. Bad input
4. Subscript out of range
5. Pointer out of range (seg fault in C)
D. Exception handling in languages that do not provide exception-handling
capabilities (e.g., C)
1. Function returns a status flag
a. program unit that calls the function must check the status
flag and take appropriate action
2. Caller passes the name of a function that handles the
exception to the callee
a. The callee calls the function with appropriate parameters
if an exception occurs.
b. disadvantage--if several exceptions must be handled, several
exception handlers must be provided with every call
3. Programmer uses setjmp/longjmp
e.g., result = setjmp(buffer);
if (!result)
protected code
longjmp(buffer, error_code) // longjmp could appear in a subroutine
} else
error handling based on error_code
a. setjmp stores registers, including pc, sp and fp, into a buffer
i. first call to setjmp returns 0
ii. call by longjmp restores the registers in the buffer and
returns control to the setjmp call. This time setjmp returns
error_code, causing control to pass to the error handling routine
b. problem: setjmp tosses all nested stack frames by restoring
the old values of sp and fp
i. any values that were held in registers by the protected
code and not written to memory are lost. Hence some
changes made by the successfully executed portion of
the protected code may be lost
ii. C's solution: programmer may use the volatile
keyword to indicate that a variable's value may
change "spontaneously", e.g., due to an I/O operation
or a concurrently executing thread
1) all C implementations must honor the volatile
keyword by immediately writing to memory any
change to a variable and always loading from
memory the value of a variable before using it.
Note that frequent loads or writes could significantly
slow down the execution of the program because loads
from memory require many more instruction cycles
then register reads and hence may stall the
processor pipeline.
4. Signal handler: Certain system-generated exceptions, such
as seg faults and bus errors, are expressed via signals.
a. It is possible to provide signal handlers to catch signals.
b. A signal handler is a function that takes an int parameter denoting
the number of the signal.
c. When the signal handler returns, the behavior of the process is
often undefined. Often the control picks up where the signal
occurred and the offending instruction is re-executed. If the
problem causing the signal has not been fixed, the signal will
be re-generated, thus causing an infinite series of handler calls.
d. You register a signal handler with the kernel by calling the
signal() function with the number of the signal and a pointer to
the signal handler
void sig_handler(int signal_num) {
if (signal_num == SIGSEGV)
printf("received SIGSEGV\n");
}
int main() {
signal(SIGSEGV, sig_handler);
}
e. It is inadvisable to write a signal handler because it is very difficult
to fix the problem, and hence your program will seem to go into an
infinite recursion unless the signal handler terminates the program.
E. Advantages to having built-in exception handling capabilities
1. Code can be considerably less cluttered
a. exception handling can be put in a separate section rather than
interspersed with the code that's trying to accomplish some
task
b. separating the exception code from the normal processing
code allows the flow of the normal processing code to be
uninterrupted, which makes it easier to read and understand
by a programmer
2. Encourages a user to consider all of the events that could
occur during program execution and consider how they should
be handled. The compiler can enforce this consideration by
flagging as an error any unhandled exception. In contrast,
a C programmer can blissfully ignore an error (maybe the
programmer does not even know the function called can raise
an error) and then have the program unceremoniously dump
core some undetermined amount of time after the software
has been released.
II. Exception Handling in C++
A. Exception Handlers
1. Specified using a try-catch clause
try {
// Code that is expected to raise the exception
}
catch(formal parameter) {
// A handler body
}
...
catch(formal parameter) {
// A handler body
}
catch(...) {
// An optional catch-all handler (the ... is actually C++
// syntax)
}
2. catch functions can have only a single formal parameter
a. parameter can be either a basic type or a user-defined class
b. user-defined classes provide a way of passing multiple
parameters.
c. the parameter can be an ellipsis (...) in which case the
handler is a catch-all handler
d. the formal parameter can be ignored by not providing a variable
name
3. any variable declared within the try block is de-allocated before
the catch statement is executed. Hence if you want access to
a variable used or assigned a value within the try block, make
sure you declare that variable outside the try block.
B. Binding Exceptions to Handlers
1. exceptions are raised using the throw command:
throw [expression];
Example: throw new BadOperatorException(operatorToken,
"bad operator token");
2. A throw without an operand can only appear in a handler
a. Such a throw reraises the exception and propagates it to
an outside program unit. If there is another, more general
exception handler that could handle the exception in the
same catch list as the handler that reraises this throw,
the handler will not be called.
b. Throws are matched to exception handlers whose formal parameter
matches the type of the thrown expression
i. A handler with a formal parameter of type T matches an
expression of type T, const T, T&, or const T&
ii. A handler with a formal parameter that is a class type T
matches any expression with class type T or whose
type is a derived class of T.
Example: class Exception {...}
class StackException {...}
try {
throw StackException();
}
All of the following catch statements will
catch the above exception:
a. catch (StackException e) {...}
b. catch (StackException &e) {...}
c. catch (Exception e) {...}
d. catch (Exception &e) {...}
You could put a const in front of any
of the formal parameter declarations
(e.g., catch (const StackException e)) and
each catch would still catch the exception
The following catch statement will not
catch the above exception because Exception *
expects a heap allocated object, not a stack
allocated object:
catch (Exception *e) {...}
iii. A handler with a formal parameter of *T matches only
exception objects that are allocated off the heap
Example: try {
throw new StackException();
}
catch (StackException &e) {
// does not catch the exception
...
}
catch (StackException e) {
// does not catch the exception
}
catch (StackException *e) {
// catches the exception because it matches
// the the type of the exception object,
// which is (StackException *).
}
1) It is bad form to throw an exception object that
is allocated from the heap because the catcher
has to remember to de-allocate the object.
2) It is better to throw an exception object that
is allocated from the stack because C++ will
automatically de-allocate the object.
3) If you throw an object, modify the object, and
then rethrow it, use a reference parameter to
catch the object. If you use a value parameter,
C++ will copy the original thrown
object, not the modified object
Example: throw StackException();
Right: catch (StackException &e) {
e.setValue(10); throw; }
Wrong: catch (StackException e) {
e.setValue(10); throw; }
c. The throw is matched by examining the catch statements that
follow the try statement in sequential order. That means
that exception handlers with more restrictive types should
be placed before exception handlers with more general types.
i. If the throw cannot be matched locally then it is
dynamically propagated up the call chain.
C. Continuation
1. After a handler completes execution, control flows to the first
statement following the try construct (the statement
immediately after the last catch statement).
2. If the exception was thrown out of the function and up the
call stack, then all stack frames up to the catching
function are popped off the stack and any stack-allocated
local variables/parmeters are de-allocated.
a. need to be careful that heap-allocated objects get
de-allocated. This may require catching an exception
you might otherwise not catch. For example:
void g() {
throw std::exception();
}
void f() {
int *x = new int;
*x = 3;
g();
delete x;
}
int main() {
try {
f();
} catch (std::exception e) { ... }
}
Because f() did not catch the exception thrown by g(),
the memory pointed to by x never gets deleted. To
fix this problem, you would need to put a try/catch
clause into f() and have the catch clause delete x.
D. Other Design Choices
1. The standard template library provides a std::exception class
a. Included using <exception>
b. Contains a virtual what method that returns a char string
denoting the type of exception:
virtual const char* what();
For the exception class it returns the string "std::exception".
c. There are several subclasses of interest, all of which can
be caught by your program
exception
bad_alloc: thrown by new on allocation failure (this is
why you don't check to see if new returns NULL--it
does not--it throws a bad_alloc error)
bad_cast: thrown by dynamic_cast on failure
runtime_error: thrown when certain runtime conditions
occur
underflow_error: thrown when an arithmetic underflow
occurs
overflow_error: thrown when an arithmetic overflow
occurs
system_error: thrown by the OS for certain errors
and includes an error code that is typically
specific to that platform and is non-portable
logic_error
out_of_range: thrown by vector, queue, dequeue,
and some other std classes on out of range errors
length_error: thrown by vector or string on resizing
error
2. Exceptions cannot be disabled
3. System-detected exceptions cannot be handled except those
thrown as system_error (e.g., seg fault and bus error
cannot be handled via this exception handling approach
because these exceptions do not generate C++
exceptions but instead generate signals)
4. You can specify which exceptions get thrown out of a function
without being handled using the throw keyword. For example:
void f() throw (DivisionByZeroException) {...}
a. The throw statement was deprecated in the C11 standard and
should not be used any more
b. The C++ compiler does not verify whether or not you
throw other exceptions out of the function, so this option
is primarily for documentation purposes.
5. Be careful when you throw out of a constructor. When you throw out
of a constructor:
a. the destructor for the object being constructed will not
be called.
b. destructors for member objects contained in that object's
class will be called
c. the memory for the object that was being constructed will
be freed. For example, if the object was being
allocated off the heap, the memory will be
automatically returned to the heap.
The fact that the destructor is not called means that any
resources you allocated in the constructor before the call
will not be freed. Specifically that means that any memory
you allocated will not be freed. That means that either the
exception handler must free any allocated resources or that
the resources should be allocated in the member objects, and
since the member object's destructors are called, the resources
will get freed. For example:
class Stack {
protected:
vector data;
public:
Stack(int size) : data(size) {
throw exception();
}
};
Since data is a member object, its destructor gets called
and its memory is automatically de-allocated when the
throw occurs.
E. Example:
// superclass for all array exceptions. It can catch any array
// exception that is thrown
class ArrayException {
public:
virtual void response() = 0;
};
// thrown if the program tries to create an array whose size is
// negative
class ArraySizeException : public ArrayException {
public:
int size;
ArraySizeException(int s) : size(s) {}
void response() { printf("Error: The array size of %d is negative\n",
size);
}
};
class ArrayOutOfBoundsException : public ArrayException {
public:
int index; // the index that was tried
int size; // the size of the array
public:
ArrayOutOfBoundsException(int i, int s): index(i), size(s) {}
void response() {}
};
// note that all throws do not involve the allocation of memory
// off the heap. Instead they throw a stack-allocated object
class SafeArray {
protected:
int *data;
int size;
public:
SafeArray(int s = 10): size(s) {
if (size <= 0) throw ArraySizeException(size);
data = new int[s];
for (int i = 0; i < size; i++)
data[i] = 0;
}
void set(int index, int value) {
rangeCheck(index);
data[index] = value;
}
int get(int index) {
rangeCheck(index);
return data[index];
}
protected:
void rangeCheck(int index) {
try {
if ((index < 0) || (index >= size))
throw ArrayOutOfBoundsException(index, size);
}
// note the rethrow of the error condition
catch (ArrayOutOfBoundsException &e) { printf("caught it\n"); throw; }
}
};
int main() {
SafeArray *myArray;
try {
myArray = new SafeArray(100);
myArray->set(3, 10);
myArray->get(-1);
}
catch (const bad_alloc &e) {
fprintf(stderr, "Out of memory error occurred while trying to allocate memory for myArray\n");
}
catch (const ArrayOutOfBoundsException &e) {
fprintf(stderr, "index %d is out of range. Must be between 0 and %d\n", e.index, e.size);
}
for (int i = -2; i <= 100; i++) {
try {
myArray->set(i, i);
if (i == 50)
new SafeArray(-10);
}
catch (const ArrayOutOfBoundsException &e) {
fprintf(stderr, "index %d is out of range. Must be between 0 and %d\n", e.index, e.size);
}
catch (ArrayException &e) {
e.response();
}
}
}
Executing this code produces the following output:
caught it
index -1 is out of range. Must be between 0 and 100
caught it
index -2 is out of range. Must be between 0 and 100
caught it
index -1 is out of range. Must be between 0 and 100
Error: The array size of -10 is negative
caught it
index 100 is out of range. Must be between 0 and 100
III. Exception Handling in Java
A. Try-with-resources statement: In addition to a normal try, you can
have the try statement declare one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.
1. Such a statement is called a try-with-resources statement.
2. Example: try (Scanner buffer = new Scanner(line)) { ... }
B. Types of Exceptions
1. All Java exceptions are descendants of the Throwable class.
Throwable provides three very helpful methods:
a. printStackTrace(): prints the standard error message for
this class plus a record of the method calls leading
up to this exception. This method should not be redefined by
subclasses.
b. getMessage(), toString(): both methods return a string object
that contains the standard error message for this class.
Typically each subclass redefines this method.
2. Java pre-defines two exception classes that are subclasses of
Throwable
a. Error: This class and its descendants are related to errors
that are thrown by the Java virtual machine, such as running
out of heap memory.
i. These exceptions are never thrown by user programs and
should never be handled by user programs
ii. Your program will be terminated when one of these errors
occurs
b. Exception: Has two descendents
i. IOException: Thrown when an input or output operation
has an error. It has numerous exceptions declared
as subclasses, including EOFException and
FileNotFoundException
ii. RuntimeException: All other run-time errors
1. Java provides a number of pre-defined exception classes
such as ArrayIndexOutOfBoundsException and
NullPointerException
--User-defined exceptions should subclass Exception
C. Exception Handlers
1. Same form as C++ except that all formal parameters must be
present (can't use ellipsis).
2. Ellipsis can be simulated by catching an Exception object.
a. To get the specific name of the class that was thrown,
use Java's getClass() method to get the class object and
then the getName() method to get the class's name:
catch (Exception genericObject) {
String name = genericObject.getClass().getName();
...
}
D. Exception Binding: Same as C++
E. Continuation: Same as C++
F. Differences from C++
1. All thrown objects are allocated off the heap, so all throw
statements have the form:
throw new ExceptionObject;
2. Rethrowing an object: In Java, when you rethrow an object you
must specify the object that you are rethrowning. In C++,
you simply call throw.
a. In Java since all exception objects are allocated off the
heap you do not have to worry about what happens when you
modify an exception object. The modified object is automatically
passed by the rethrow statement
Example: Java C++
catch(StackException e) { catch(StackException &e) {
e.setValue(10); e.setValue(10);
throw e; throw;
} }
3. Declaring unhandled exceptions: In Java, if a method throws an
exception that it does not handle, then it should declare
that it does not do so by using the throws statement.
a) Syntax: ret-type methName(param-list) throws exception-list
{ ... }
where exception-list is a comma separated list of exception
names
Example: char readToken() throws IOException {
return (char) System.in.read();
}
Notice that you must worry about exceptions thrown by
methods that you call. Hence the throws statement is
required both for exceptions that a method explicitly throws
and for exceptions that are generated but not handled by
methods it calls
b) Exception to the rule (pardon the pun :)): If the exception
is a subclass of Error or RuntimeException, then the method
does not need to specify the exception in the throws list
c) If you fail to specify an unhandled exception in the throw
list, and it is not subject to the above exception, the Java
compiler will generate an error message
i) In C++ it is not required for a method to declare that it
does not handle an exception (although it is optional
and can be done using the throw statement).
4) The finally clause: In Java, the finally clause provides a
way to execute a block of code regardless of whether the
try exits normally or abnormally. You might need to do this
to clean up system state, such as closing a file.
a) The finally statement is placed after the last catch statement
b) The code in a finally statement is executed even if a return
statement is executed within the try or one of the catch
clauses
c) The code in a finally statement is executed even if no catch
clause catches the exception
Example:
public class temp {
public temp() {}
public void execute() throws MyException {
try {
throw new MyException();
}
finally {
System.out.println("Leaving execute via finally");
}
}
public static void main(String [] args) {
temp foo = new temp();
try {
foo.execute();
} catch (MyException e) {
System.out.println("caught MyException in main");
}
System.out.println("Leaving main");
}
}
class MyException extends Exception {}
Executing this code generates the following output:
Leaving execute via finally
caught MyException in main
Leaving main
G. Java Example: Here's the Java code that implements the same C++
program shown earlier. Notice that I had to add an additional
catch clause to the first try statement in main since Java requires me
to handle all exceptions that are declared to be thrown by a
method. In the C++ example I did not handle the ArraySizeError
exception in the first try statement but in Java I must.
class SafeArray {
int data [];
int size = 10;
// Note that we assume the new operator succeeds. If it does
// not, Java throws an OutOfMemoryError. Note that this is
// a subtype of Error, which means your program should not
// attempt to handle it--your program will be terminated
public SafeArray(int s) throws ArraySizeError {
size = s;
if (size <= 0) throw new ArraySizeError(size);
data = new int[size];
for (int i = 0; i < size; i++)
data[i] = 0;
}
public void set(int index, int value) throws ArrayOutOfBoundsError {
rangeCheck(index);
data[index] = value;
}
public int get(int index) throws ArrayOutOfBoundsError {
rangeCheck(index);
return data[index];
}
void rangeCheck(int index) throws ArrayOutOfBoundsError {
try {
if ((index < 0) || (index >= size))
throw new ArrayOutOfBoundsError(index, size);
}
catch (ArrayOutOfBoundsError e) {
System.err.println("caught it");
throw e;
}
}
public static void main(String args[]) {
SafeArray myArray = null;
try {
myArray = new SafeArray(100);
myArray.set(3, 10);
myArray.get(-1);
}
catch (ArrayOutOfBoundsError e) {
System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size);
}
catch (ArrayError e) { // catches the ArraySizeError exception
// thrown by SafeArray
e.response();
System.exit(1);
}
for (int i = -2; i <= 100; i++) {
try {
myArray.set(i, i);
if (i == 50)
new SafeArray(-10);
}
catch (ArrayOutOfBoundsError e) {
System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size);
}
catch (ArrayError e) {
e.response();
}
}
}
}
abstract class ArrayError extends Exception {
abstract public void response();
}
class ArraySizeError extends ArrayError {
public int size;
public ArraySizeError(int s) { size = s; }
public void response() {
System.err.println("Error: The array size of " + size + " is negative");
}
}
class ArrayOutOfBoundsError extends ArrayError {
public int index;
public int size;
public ArrayOutOfBoundsError(int i, int s) {
index = i;
size = s;
}
public void response() {}
}
H. Another Java Example: Stack