INFO: Using Try/Finally Properly

ID: Q83670


The information in this article applies to:


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