Visual C++ exception handling

Thursday, 15 August 2002

Solution overview

So the basic problem is this. In a pre-2005 Visual C++ program with default settings, Win32 exceptions can sometimes be caught with catch(...) and will sometimes fail to unwind the stack properly. Here I describe three solutions to this, each requiring different actions on the part of the programmer. They are:

1. Don’t catch Win32 exceptions. If you never catch Win32 exceptions, they will always cause the program to crash. This is perhaps the most obvious result, and is how most computer programs work. When a Win32 exception occurs, you will be able to use your normal debugging tools to isolate and correct the error that led to the exception.

2. Ensure the stack is unwound correctly when catching Win32 exceptions. This approach ensures that Win32 exceptions are always caught by catch(...) and will unwind the stack properly when caught. The disadvantage here is that no information about the exception is available at the point where it is caught.

3. Turn Win32 exceptions into C++ exceptions. This approach fully integrates Win32 exceptions into the normal C++ exception handling mechanism. When a Win32 exception occurs, it is automatically turned into a rich C++ exception object and handled according to the normal C++ exception rules. This is perhaps the most elegant approach, but it requires the most work on the part of the programmer.

Solution 1: Don’t catch Win32 exceptions

If you never catch Win32 exceptions, they will always cause the program to crash. This is perhaps the most obvious result, and is how most computer programs work. In Visual C++ 2005, you can’t catch Win32 exceptions directly, so there’s no problem. However, in pre-2005 Visual C++, you must avoid using catch(...) anywhere.

The problem with this is, of course, that catch(...) can be quite useful. Even when the developer knows which types of exception can be thrown, and therefore writes:

catch (exceptionBase&) {
    // clean up
    throw;
}

It may better express the programmer’s intention to write this instead:

catch (...) {
    // clean up
    throw;
}

In other words, “if any exception occurs, clean up and rethrow it”. It’s a shame to have to avoid this.

Solution 2: Ensure the stack is unwound correctly

In pre-2005 Visual C++, it is possible to ensure that Win32 exceptions are always caught by catch(...) and will unwind the stack properly. However, no information about the exception will be available. This happens if you compile with the “asynchronous” exception-handling model.

In the asynchronous exception-handling model, the compiler assumes that any code can cause an exception. This is actually (more-or-less) true of any pre-2005 Visual C++ code that has a catch(...) handler, since catch(...) always catches Win32 exceptions.

To enable the asynchronous exception-handling model, compile with the /EHa compiler switch.

Listing 4: Unwinding the stack

 1  class thing
 2  {
 3  public:
 4      thing() { std::cerr << "hello "  ; }
 5      ~thing() { std::cerr << "goodbye "; }
 6  };
 7  
 8  int main() {
 9      try {
10          std::cerr << "try ";
11          thing t1;
12          char* cp = 0;
13          *cp = 'a';
14      }
15      catch (...) {
16          std::cerr << "catch";
17      }
18  }

In Visual C++ 2005, this simply prints “try hello” and then crashes. Nice and simple.

Compiled with the default /GX switch on pre-2005 Visual C++, Listing 4 outputs “try hello catch“. It fails to unwind the stack correctly; the t1 object destructor does not get called. If you compile with /EHa, the stack will unwind properly and you get “try hello goodbye catch” as expected.

Therefore, if a pre-2005 Visual C++ program contains catch(...), then it should be compiled with asynchronous exception handling enabled (/EHa). Otherwise, if the catch(...) catches a Win32 exception, “some of the unwindable objects in the function where the exception occurs may not get unwound” (Visual C++ Programmer’s Guide), which could lead to resource leaks or worse.

Note that this approach requires the compiler to insert complete cleanup code in every try block, even if it could normally determine that no C++ exception can be thrown from within it. This may increase overall code size.

Solution 3: Turn Win32 exceptions into C++ exceptions

Perhaps the best solution, especially in a large program, is to transform Win32 exceptions into C++ objects that can be handled according to the normal C++ exception rules. This approach works equally well, and is equally useful, on Visual C++ 2005 and on earlier versions.

To get this behaviour, you must create your own class and write an exception handler to “catch” Win32 exceptions and throw C++ exceptions. You must also compile with the /EHa compiler option to enable asynchronous exception handling. At runtime, you install your handler by calling the global function _set_se_translator().

Background information on _set_se_translator() is available in Microsoft’s MSDN Library. There’s also a bare-bones example of this approach to exception handling.

The nice thing about this scheme is that the C++ exception object created can contain quite thorough information. For example, it could contain the kind of exception (divide by zero, access violation, etc.), the address at which the exception occurred, and even a stack trace.

So here is the sequence of events when a Win32 exception occurs in a program using this scheme.

  1. The program installs its Win32 exception handler.
  2. The program runs for a while.
  3. At a crucial, deeply-nested point in the program, an access violation occurs.
  4. The Win32 exception handler is called, with details of the exception.
  5. The Win32 exception handler creates a new C++ object, initialising it with detailed information about the exception.
  6. The Win32 exception handler throws the C++ object as a C++ exception.
  7. The call stack unwinds to the nearest catch block for the C++ exception type. All destructors are called correctly, and in general everything is cleaned up as expected.
  8. The catch block has access to full information about the Win32 exception, though the C++ exception. It writes a log message for the programmers, whose debugging task is now much easier.

Pages: 1 2 3

Tags:

41 comments

You can leave a comment, or trackback from your own site.

Leave a comment