Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
When an exception is thrown, there s considerable runtime
overhead (but it s good overhead, since objects are cleaned up
automatically!). For this reason, you never want to use exceptions as part of
your normal flow-of-control, no matter how tempting and clever it may seem.
Exceptions should occur only rarely, so the overhead is piled on the exception
and not on the normally executing code. One of the important design goals for
exception handling was that it could be implemented with no impact on execution
speed when it wasn t used; that is, as long as you don t throw an
exception, your code runs as fast as it would without exception handling.
Whether this is true depends on the particular compiler implementation you re
using. (See the description of the zero-cost model later in this section.)
You can think of a throw expression as a call to a
special system function that takes the exception object as an argument and backtracks
up the chain of execution. For this to work, extra information needs to be put
on the stack by the compiler, to aid in stack unwinding. To understand this,
you need to know about the runtime stack.
Whenever a function is called, information about that
function is pushed onto the runtime stack in an activation record instance (ARI), also called a stack frame. A typical stack frame contains
the address of the calling function (so execution can return to it), a pointer
to the ARI of the function s static parent (the scope that lexically contains
the called function, so variables global to the function can be accessed), and
a pointer to the function that called it (its dynamic parent). The path
that logically results from repetitively following the dynamic parent links is
the dynamic chain, or call chain, that we ve mentioned previously
in this chapter. This is how execution can backtrack when an exception is
thrown, and it is the mechanism that makes it possible for components developed
without knowledge of one another to communicate errors at runtime.
To enable stack unwinding for exception handling, extra
exception-related information about each function needs to be available for
each stack frame. This information describes which destructors need to be
called (so that local objects can be cleaned up), indicates whether the current
function has a try block, and lists which exceptions the associated
catch clauses can handle. There is space penalty for this extra information, so
programs that support exception handling can be somewhat larger than those that
don t. Even the
compile-time size of programs using exception handling is greater, since the
logic of how to generate the expanded stack frames during runtime must be
generated by the compiler.
To illustrate this, we compiled the following program both
with and without exception-handling support in Borland C++ Builder and
Microsoft Visual C++:
//: C01:HasDestructor.cpp {O}
class HasDestructor {
public:
~HasDestructor() {}
};
void g(); // For all we know, g may throw.
void f() {
HasDestructor h;
g();
} ///:~
If exception handling is enabled, the compiler must keep
information about ~HasDestructor( ) available at runtime in the ARI
for f( ) (so it can destroy h properly should g( )
throw an exception). The following table summarizes the result of the
compilations in terms of the size of the compiled (.obj) files (in bytes).
Compiler\Mode
|
With Exception Support
|
Without Exception
Support
|
Borland
|
616
|
234
|
Microsoft
|
1162
|
680
|
Don t take the percentage
differences between the two modes too seriously. Remember that exceptions
(should) typically constitute a small part of a program, so the space overhead
tends to be much smaller (usually between 5 and 15 percent).
This extra housekeeping slows down execution, but a clever
compiler implementation avoids this. Since information about exception-handling
code and the offsets of local objects can be computed once at compile time,
such information can be kept in a single place associated with each function,
but not in each ARI. You essentially remove exception overhead from each ARI
and thus avoid the extra time to push them onto the stack. This approach is
called the zero-cost model of exception handling, and the optimized storage mentioned earlier is known as the shadow
stack.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |