Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
73 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 Termination
 Handlers

 Exception Handlers

 Printable version

 


Termination Handlers – Basics

A termination handler is a block of code that gets called on early exit from a function or any of the functions it calls. This can be useful for providing dynamic clean-up, similar to C++ destructors (with the exception that C++ destructors do not get called when a hardware exception is raised).

To build a termination handler, simply enclose the code you want to guard in a try block and follow it by clean-up code inside the finally block, like so:

__try
{
  // Guarded code
}
__finally {
  // Termination handler
}

The code above will execute as follows: unless there is some interruption – officially termed a premature exit, the code in the try block will execute, followed by code in the finally block. If there is a premature exit, the instruction pointer jumps from the current machine instruction to the first instruction inside the finally block. There are two types of premature exits: those invoked by an early jump from the try block, such as returning from the function, and those that are caused by SEH exceptions being raised.

Whenever there is a change in control that results in the instruction pointer of the CPU leaving the guarded code, such as a return or a goto, the system considers this a premature exit and the instruction pointer is moved to the finally block. The confusing aspect of this is the question, "what does it mean for a jump to leave the guarded block?" Generally, if a statement makes some kind of jump that is not a function call, it will leave the guarded block and cause finally code to begin executing. This happens because function calls push their return address on the stack, and the system recognizes that  very important fact, important because as long as the system can be assured that code can return to the finally block at any point, it does not consider a call to be a premature exit from the try block. This mechanism gives the termination handler technology a serious advantage over "fail gotos".

What happens when a software or hardware exception is raised? Before the code in an exception handler can execute (as described below), the system ensures that any finally code it encounters that is on the stack below the handler is run. Compare it to a fatal wound. If the finally code starts running early, you may not be quite dead yet, but you know that there isn’t anything you can do about it (probably the reason for the keyword itself).

If a premature return happens in a termination handler’s try block locally (in other words, the system executes a jump instruction), the system must do a local unwind. This means that whenever such a return happens, the system must save the variable on the stack and force execution to continue in the finally block. This is not very efficient on some processors and should be avoided, specifically by using the __leave keyword. The second kind of unwind (a global unwind) has a slightly more complex role. It is more appropriate to discuss it when I get to exception handling.

As you can see, termination handling is an interesting compiler extension.  However, it is not meaningful until seen applied in some context.

Applying Termination Handlers

So, what is the practical benefit of termination handlers? In a situation where a function could fail before termination, you may find it wise to use a termination handler even if you can see no reason in the world for an exception to be raised. In fact, the blessing of THs is that they are applicable to almost any task, if

// Termination handler example with summation program:
// read data from file and accumulate the sum

int CalcSum (const char* pszfn, int iCount, int* pfOK)
{
  // These variables are local to the function
  int fSuccess = 0, iSum;
  FILE* pf;
  int* pMem = NULL;

__try { // Some variables needn't be accessed in the finally block int i; // Begin the try block by opening a file to read // the numbers from pf = fopen (pszfn); // If that fails, we bail out if (!pf) __leave; // Allocate some memory pMem = malloc (iCount * sizeof(int)); if (!pMem) __leave; // Read from it if (fread (pMem, sizeof(int), iCount, pf) < sizeof(iPrvNumber) __leave; // Call Summate for (i = 0; i < iCount; i+=2) { Summate (pMem[i], &iSum); } // Signify that all has gone OK fSuccess = 1; } __finally { // Take care of the cleanup fclose (pf); free (pMem); } // Set up the success out parameter *pfOK = fSuccess; // And the actual return value, too return iSum; } void Summate (const int* pSrcArray, int* pDest) { // For example. sum two numbers or 1/2 chance // crash the entire system if (rand () % 2) { // Randomly crash the scene (don't try this at home) short *p = NULL; *p = 0xBEEF; // Random value } else { *pDest = pSrcArray[0] + pSrcArray[1]; // Correct action } }

used correctly. The listing above should provide you with a good example of a function that makes good use of a TH. There is one keyword you will not recognize, and that is __leave. This keyword is described in detail below.

In this code, we have several conditions for early termination. The unfamiliar __leave keyword is actually quite simple: it is an alias for a goto statement that jumps to the closing brace of the try block, essentially skipping any code still left in it. This method should sound familiar if you have worked in BASIC: it is the same ON ERROR GOTO principle applied there. In effect, we are saying that we want the lines of code in the finally block to execute no matter what happens in the try block. If you examine the Summation function, you will notice that there is a 50% chance it will attempt an invalid memory access and thus raise an exception. If that happens, the system will see that the program failed to handle the exception and will handle the exception on its own, displaying the Illegal Operation dialog box on Windows 9x. However, prior to that the statements in the finally block are executed. Both of these occurences are normal, since exceptions, akin to their name, occur quite rarely (I can’t imagine anyone writing a Summation function as in the example unless they want to give their marketing manager a heart attack).

Why use __leave and not return? As said before, use of __leave prevents local unwinds from occurring and slowing down the entire setup. In general, Jeffrey Richter recommends you to avoid putting any flow-of-execution modifiers in the try block. When you wish to use a loop, you should be absolutely sure that the loop you are writing is entirely inside the try block and that your jump will remain within the block. This is demonstrated by the continue in the for loop.

Why not simply use the "fail goto" method? You can use such a structure for simple code setups, of course, but this is going to start to fail you when you are talking about more than one function or even nested try-finally blocks. If Summation had called another function, say, GetDC, and GetDC failed, how would the failure be reported? One way is to use the infamous return-value-chain, but its complexity was one of the reasons exception handling was invented. In reality, all the inner function has to do to report failure in a try-finally situation is raise a software exception. A function somewhere at the top will have a try-except block that will handle the exception. While the system walks up the stack to run the code in the except block, all the finally blocks execute, cleaning up after whatever was in the try block. Termination handling is clean and effective, and has no easy alternative in either ANSI C or standard C++.




Next : Exception Handlers