INFO: Using Try/Finally Properly
ID: Q83670
|
The information in this article applies to:
-
Microsoft Win32 Application Programming Interface (API), included with:
-
Microsoft Windows NT, versions 3.10, 3.50, 3.51, 4.0
-
Microsoft Windows 95
SUMMARY
When you use the try/finally exception handling, it helps to provide a
robust application. If you use it incorrectly, however, it can cause
unnecessary overhead. Any flow of control out of the try/finally block is
considered an abnormal termination that can cause hundreds of instructions
to be executed on an Intel x86 system, and thousands on RISC machines. This
occurs even if the control leaves via a control statement on the very last
statement of the try body or finally clause.
MORE INFORMATION
Any flow of control out of the try/finally block using the following
statements causes abnormal termination: return, goto, continue, or break.
Notice that these statements only cause abnormal termination if they cause
control to leave the try block or finally clause. Using a break statement
in a loop inside the try block or finally clause only causes control to
exit the loop, not the try/finally block. This is perfectly acceptable. On
the other hand, use of a break statement that causes control to leave the
try block or finally block results in abnormal termination and inefficient
execution.
Abnormal termination also occurs if an exception occurs. This cannot be
avoided.
To avoid abnormal termination of the try/finally block, control should
leave the try/finally block by falling through the bottom of the block.
Control can also leave the try block by executing the __leave statement.
The __leave statement allows for immediate termination of the try block
without causing abnormal termination and its performance penalty. Check
your compiler documentation to determine if the __leave statement is
supported.
Execution of code inside the try block that causes an exception causes the
finally clause to be executed. Execution of code inside the try block that
causes the program to end prevents the finally clause from executing. For
example, any code that calls the ExitProcess API, the exit() function, the
abort() function, or any other such function causes immediate termination
and the finally block is not executed.
The following example demonstrates incorrect flow of control out of the
try body:
/* Incorrect use of try/finally */
VOID Foo(...)
{
__try {
if (...) {
.
.
return; // causes abnormal termination
}
.
.
}
__finally {
if (...) {
.
.
return; // causes abnormal termination
}
.
.
}
return;
}
In the example above, abnormal termination of the try body occurs when the
return statement in the middle of the try body is executed. On the other
hand, if the result of the condition is false, then the return statement is
avoided and efficient execution of the finally clause occurs because this
is not an abnormal termination. Abnormal termination also occurs if the
return statement is executed in the finally clause.
The abnormal termination can be avoided in the above example a couple of
ways. First, the return statement in the try body can be replaced with a
__leave statement. Check your compiler documentation to determine if this
statement is recognized by your compiler. If your compiler doesn't support
such a statement, then you can use the second method. The second method
avoids abnormal termination in both the try block and the finally clause by
note executing a return statement at all. The solution is to move the
return statement AFTER the end of the finally clause. This causes execution
to fall through the try body normally.
For example, then following code works properly:
/* Correct use of try/finally */
VOID Foo(...)
{
__try { // only way to exit is to 'fall through'
if (...) {
.
.
}
else {
.
.
}
}
__finally { // only way to exit is to 'fall through'
if (...) {
.
.
}
else {
.
.
}
}
return;
}
When abnormal termination occurs, hundreds to thousands of instructions are
executed. A stack unwind must occur that causes a backward search through
frames to determine if there are any termination handlers to be called. On
an Intel x86 system, this executes the C runtime handlers and examines the
handler list. On a RISC machine, this also causes the function table to be
searched and the prologue of each intervening function to be executed
backwards interpretively.
Structured Exception Handling Details
Structured exception handling has two stages. First, the system searches
through the exception stack executing each exception filter. Evaluation of
the exception filter results in one of the following: the machine state is
restored and execution continues at the point of the exception, or the
system continues searching for an exception handler repeating this stage,
or the system transfers control to the exception handler (stage 2). In
stage 2, thread execution continues in the stack frame where the exception
handler is found. If the handler is not in the stack frame where the
exception occurred, the system unwinds the stack, leaving the current stack
frame and any other stack frames until it is back to the exception
handler's stack frame. Before the exception handler's frame is reached
during this unwind, each finally clause is executed for each try block that
terminated as a result of the transfer of control to the exception handler.
An abnormal termination in the finally clause aborts this second stage.
Instead of returning to the system unwinder, the finally block returns to
the enclosing function's caller (for instance, Foo()'s parent). The filter
function may have set some status or performed an allocation in
anticipation of the exception handler being entered. In this case, the
intervening finally clause with the return stops the unwind, and the
exception handler is never entered.
This behavior is by design. It makes it possible for a finally clause to
stop an unwind and return a status. This is what is referred to as a
"collided unwind."
Normally this behavior is transparent to any higher-level exception
handling code. However, if a filter function stores information that it
expects to process in an exception handler (as a side effect), then it may
or may not be transparent. You should avoid storing such information in a
filter function because it is always possible that the exception handler
will not be executed because the unwind is preempted. In the absence of
storing such information, it is transparent that an exception occurred and
a collided unwind occurred if one of the descendant functions has a
try/finally block with a finally clause that preempts the unwind.
Additional query words:
SEH scope
Keywords : kbprg kbKernBase kbGrpKernBase
Version : 3.1 3.5 3.51 4.0
Platform : NT Win95 WINDOWS
Issue type : kbinfo
Last Reviewed: March 6, 1999