Visual C++ exception handling

Thursday, 15 August 2002

This article describes a problem with the default handling of exceptions in Microsoft’s Visual C++ compilers. The problem is caused by the compiler’s extension to the C++ exception handling mechanism. I then give a technique that properly exploits this extension, bringing it into line with normal C++ exception handling. This allows programs to deal with exceptions that are normally very difficult to handle, such as memory access violations.

This article was originally based on Visual C++ 6, and the same issues exist in the Visual Studio .NET C++ compiler. Finally, after at least six years, Microsoft fixed the fundamental problem in Visual C++ 2005. But to fully take advantage of this, it’s still necessary to implement an exception translation scheme like the one described in this article.

Win32 hardware exceptions

Under Visual C++, certain exceptional runtime conditions can be treated something like C++ exceptions. These exceptions are raised by the OS for events like memory access violations, division by zero, and so on. A (presumably) full list is in Microsoft’s MSDN Library under “EXCEPTION_RECORD”.

They are referred to in the documentation as “hardware exceptions”, “C exceptions”, “structured exceptions” and “C structured exceptions”. Actually, they are neither C-specific nor structured (at least compared to C++ exceptions). I refer to them as “Win32 hardware exceptions” or just “Win32 exceptions” because they are specific to the Win32 operating systems and hardware on which they run.

Visual C++ programs may be unstable by default

Visual C++ is a good compiler. But, like all compilers, it has some bad features. The default handling of Win32 exceptions in pre-2005 versions is one of them.

In general, when a computer program program causes (say) a memory access violation, the program could either:

  1. continue in a consistent state;
  2. crash immediately; or
  3. continue in an inconsistent state, possibly misbehaving or even crashing later.

The first option is clearly the best. If this is is not possible, the second option is acceptable (and usually easy to achieve), since it maximises chances of finding the bug. The second option is taken by Visual C++ 2005.

Unfortunately, the default behaviour in a pre-2005 Visual C++ program is the third option. This behaviour makes it very difficult to find the bug that lead to the problem in the first place; the program may make it through testing and into production before the bug comes to light. A good, old-fashioned crash is much more obvious, and therefore easier to find and debug.

Now, sometimes the third option may be acceptable. For example, if a program is required to use a third-party library, and the library has bugs of this kind, and the developers have no access to the library source code, then the first option may be ruled out, and it may be considered better for the program to limp along rather than die. In most cases though, we want to handle such errors gracefully and find the bug that causes them.

Default handling of Win32 exceptions may be dangerous

According to the default behaviour in pre-2005 Visual C++, when a Win32 exception occurs (possibly nested) in a try{} block that has a corresponding catch(...) block, the call stack is unwound and the catch block will be entered. (This in itself is strange behaviour: catch is meant to catch C++ exceptions only, so why anyone would expect it to catch an access violation or division by zero is beyond me.)

However, the stack may not be unwound properly as it would if it were a normal C++ exception being caught; in particular, destructors of some stack-based objects may not be called. Furthermore, no information will be available about the Win32 exception. (Of course, this is always a problem with catch(...) blocks.)

As a result, the program will now be in an unstable state, and there is no way to determine where the problem occurred that triggered the Win32 exception.

This problem does not occur in Visual C++ 2005, where catch(...) never catches Win32 exceptions. This makes a big difference to program stability and consistency, but there is still a lot of room for improvement, as discussed below.

This is a breaking change in Visual C++ 2005. In pre-2005 Visual C++, catch(...) can catch Win32 exceptions. In Visual C++ 2005, it never does. This change, which could break existing code, was introduced very quietly. Perhaps it’s an unintended (but welcome) side effect of the integration with .NET and Managed C++. We know it’s a breaking change because it breaks Microsoft’s own example code on their Exception Handling Differences page. We know it was introduced quietly because it is not listed on their Breaking Changes in the Visual C++ 2005 Compiler page.

Synchronous exception handling and catch(...)

The source of this instability lies in the interaction of two aspects of the behaviour of default programs: the synchronous exception-handling model, and the use of catch(...).

Synchronous exception handling is the default. The default /GX option (/EHsc in Visual C++ 2005) causes Visual C++ programs to be compiled with the “synchronous” exception-handling model, where the compiler assumes that exceptions can only be thrown with the throw statement (which is true in standard C++). This allows certain optimisations: for example, if the compiler can determine that nothing will be thrown inside a given try block, then it can avoid generating all the code to unwind the stack (e.g. call appropriate destructors) for the catch.

catch(...) always catches Win32 exceptions. In pre-2005 Visual C++, a catch(...) block will by default be entered whenever a Win32 exception (e.g. divide by zero or memory access violation) occurs in its try block. This is not much use, because by the time the catch block is entered, no information about the error is available.

Therefore, for any pre-2005 Visual C++ program that contains catch(...) and that was compiled with default settings, any line that (for example) dereferences a pointer can throw a catchable exception; but the compiler will optimise based on the assumption that it can’t.

The second catch block in Listing 1 illustrates this.

Listing 1: Demonstrating the problem

 1  class whatever
 2  {
 3  public:
 4      whatever(int i) : id(i) { std::cerr << id << "-constructor "; }
 5      ~whatever(void) { std::cerr << id << "-destructor " ; }
 6  private:
 7      int id;
 8  };
 9  
10  int reciprocal(int i) {
11      return 1/i;
12  }
13  
14  int main() {
15      int k = 0;
16      std::cerr << "first-try ";
17      try {
18          whatever w1(1);
19          int r = reciprocal(0);
20      }
21      catch (...) {
22          std::cerr << "caught ";
23      }
24      std::cerr << "second-try ";
25      try {
26          whatever w2(2);
27          int r = 1/k;
28      }
29      catch (...) {
30          std::cerr << "caught ";
31      }
32      std::cerr << "exiting ";
33  }

Under Visual C++ 2005, this program behaves as you’d expect a program to behave on any platform: it prints “first-try” and then crashes.

Under pre-2005 Visual C++, line 19 calls a function that throws a Win32 exception. As expected, w1‘s destructor is called, and then the exception is caught at line 22. So far, so good.

But at line 27 we have a direct divide by zero. This throws a Win32 exception as before, and the exception is caught at line 30, but w2‘s destructor never gets called. This happens because the compiler has incorrectly assumed that the try block starting at line 25 will not throw any exceptions.

The previous try block behaves differently. Since it contains a function call, the optimiser has assumed that it may throw, so has not optimised away the destructor call. But note that if this program is compiled with the /O1 option to minimise code size, then neither destructor gets called.

Note that this is arguably not a bug; that’s how the compiler is meant to behave when synchronous exception handling is enabled. As the Visual C++ documentation states:

Catching hardware exceptions is still possible with the synchronous model. However, some of the unwindable objects in the function where the exception occurs may not get unwound, if the compiler judges their lifetime tracking mechanics to be unnecessary for the synchronous model.

In other words, catching Win32 exceptions (and asynchronous exception handling in general) under the synchronous exception handling model is at the compiler’s discretion. Unsurprisingly, the compiler’s discretion depends on the optimisation level.

How optimisation affects exception handling

The following near-identical programs illustrate how the optimisation level in Visual C++ can have a profound effect on the behaviour of a program when a Win32 exception occurs.

Listing 2

 1  int main() {
 2     try {
 3
 4        char* bad = 0;
 5        *bad = 0; // Generates EXCEPTION_ACCESS_VIOLATION
 6     }
 7     catch(...) {
 8        std::cerr << "catch";
 9     }
10  }

Listing 3

 1  int main() {
 2     try {
 3        std::cerr << "try ";
 4        char* bad = 0;
 5        *bad = 0; // Generates EXCEPTION_ACCESS_VIOLATION
 6     }
 7     catch(...) {
 8        std::cerr << "catch";
 9     }
10  }

Listing 2 and Listing 3 are identical except that Listing 2 prints some text at line 3. According to the C++ standard, both programs produce undefined behaviour. However, in general you could reasonably expect Listing 2 just to crash, and Listing 3 to print “try” and then crash. Using Visual C++ with default options, you may instead expect Listing 2 to print “catch“, and Listing 3 to print “try catch“, as the Win32 exception is caught.

Actually, under pre-2005 Visual C++ with default options, the programs behave inconsistently.

Listing 2 crashes, because the catch(...) has been ignored by the optimising compiler. (This depends on the compiler switches.)

Listing 3, however, prints “try catch” as the catch(...) catches the access violation Win32 exception. (In fact, this occurs regardless of which compiler switches are used.)

Visual C++ 2005 behaves consistently, since the catch(...) has no effect. Both programs simply crash.

Pages: 1 2 3

Tags:

41 comments

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

  1. Jason, it can take a bit of investigation and preparation to set everything up, but once you get it working it’s really nice. Now you too can recover from null pointer references, just like all your fancy C# friends!

  2. Great description of a potentially tricky issue. I rarely read such well written pieces.

    The C++ boost (v 1.33.1) unit testing library pre-built didn’t get built with the ‘async exception model’ and couple of the self-tests broke in a way which I think you’ve just explained for me.

    Also, good font and layout choice, which made it not only worthwhile to read, but also pleasant to read.

  3. Motti Lanzkron

    VS8 allows you to keep the (bad) old behaviour by using the /EHa flag (project properties -> C/C++ -> Enable C++ Exceptions = Yes with SEH Exceptions)

  4. Excellent article, a must!

  5. There is a pblm with me…that I want to upgrade a project that is successfully compiling in C++ compiler 5.02…I want to compile it in C++ builer 6.0..but it gives error..as in this project try and except is used …error is for except()…and error is “catch expected”…what I do to resolve this error…help me

  6. Hi,

    This article well describes the Exception Handling in C++. I am able to understand it very well. Is there any way we can get the File and line no where exception occured ?

    Raghavan

  7. Hi Raghavan, there is a way to know where the bad thing occurred: search for crashfidner. I remember it was in an article of ‘bugslayer’…

  8. Hi Raghavan,

    to do so, I#m using the predefined macros __FILE__ respectively __LINE__ .
    See MSDN library:
    http://msdn.microsoft.com/en-us/library/b0084kay(VS.71).aspx

    Cheers,
    Moni

  9. Very Nice Article.

    I have an .exe and around 15 dlls. Some of these dlls are loaded using loadlibrary.

    Would it be enough if i build the .exe with /EHa or perhaps build one of the dlls(which is actually a listening server and does the main work) with /EHa. Or do i need to build all dlls and the .exe with /EHa to be able to catch an exception occuring anywhere in the application.

  10. Thomas, it would be safest to build everything with /EHa unless you have some specific reason you need to disable it.

  11. The proposed solution to throw a C++ exception from the _set_se_translator registered handler is not a safe operation when compiling with /EHs – occasionally, you’ll get crashes in the stack unwinding code because of this. The reason is that all the instructions necessary for properly unwinding the stack at any given execution point are omitted when compiling with /EHs. Additionally, when compiling with optimizations, the compiler may elide C++ catch handlers that it determines cannot be entered (the compiler cannot infer that a Win32 exception may invoke a _set_se_translator registered handler that may throw a C++ exception) – which means the exception may not get caught where you expected it to.

    At the point that a Win32 exception occurs, the stack and objects on the stack may not be in a consistent state. Without the instructions added when compiling with /EHa, stack unwinding may result in destructors being called for partially constructed/destructed objects – which is a very bad thing. Additionally, partially constructed objects may not be unwound properly (or at all) leading to resource leaks.

    Win32 exceptions and exception handlers have all the same challenges as signals and signal handlers have on POSIX platforms. I once read about someone advocating throwing C++ exceptions from signal handlers – which is a bad idea for exactly the same reasons as described above.

    I recommend that catching of Win32 exceptions be limited to regions of code which do not require stack unwinding (ie, catch the exception in the stack frame that generated it and don’t place any C++ objects in the try/catch block). Or, use the async-exception safe longjmp/setjmp APIs (the ones in setjmp.h, NOT setjmpex.h). These longjmp/setjmp APIs will not call destructors for objects on the stack, but will ensure that the stack is unwound safely. Unfortunately, MS developers messed this up by not providing distinct interfaces to the async-exception safe setjmp/longjmp APIs (On POSIX systems, sigsetjmp and siglongjmp exist for this exact reason). You need to be very careful to ensure that setjmpex.h was not included prior to setjmp.h

  12. Thanks for your comment Tom. Just in case it wasn’t clear, I agree that _set_se_translator does not play well with /EHs. That’s why I said:

    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().

    The dire consequences you describe can indeed happen in code compiled with /EHs. But using /EHa pretty much solves all these problems. Following the approach I describe in the article should yield well-behaved and useful C++ exceptions. I’d like to know about any problems with this approach.

  13. Hi Bennett,

    I missed that you did mention that compilation with /EHa is required – sorry, my bad.

    There are two negative side effects with the use of /EHa. First, the overhead of the asynchronous unwind code affects performance and generates larger binaries. Second, compilation with /EHa causes catch(…) handlers to be entered for Win32 exceptions. Your post states that “This problem does not occur in Visual C++ 2005, where catch(…) never catches Win32 exceptions.”. This isn’t correct as the following code demonstrates:

    #include

    int main()
    {
    try
    {
    volatile int* pi = NULL;
    volatile int i = *pi;
    }
    catch(…)
    {
    std::cerr << “Caught exception” << std::endl;
    }
    return 0;
    }

    Compile this with “cl /O2 /EHa /Zi /MD main.cpp /Femain.exe” and run main.exe and you’ll see that the access violation does cause the catch(…) handler to be invoked.

    For all the reasons enumerated in your excellent post, this is disastrous for code quality and in my opinion sufficient reason to not compile with /EHa.

    Unfortunately, I was mistaken in my previous comment regarding async-exception safe setjmp/longjmp. I have since discovered that neither the “setjmp.h” or “setjmpex.h” setjmp/longjmp implementations are async-exception safe since both attempt to unwind the stack. The Microsoft documentation is confused about this – see http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx. This documentation currently contradicts itself with regard to whether or not C++ stack objects are destroyed when longjmp is called. In our environment (compiling with /EHsc), we have seen crashes with call stacks like this:

    msvcr80!raise+0x187
    msvcr80!abort+0x3d
    msvcr80!terminate+0x4d
    msvcr80!_inconsistency+0x22
    msvcr80!__FrameUnwindToState+0x4b
    msvcr80!__CxxLongjmpUnwind+0x14
    msvcr80!longjmp+0x76

    The fact that longjmp called a function named CxxLongjmpUnwind suggests that it does in fact destroy C++ stack objects while unwinding.

    This has left me in a bind. We need to do something like what you are suggesting – return to a previous execution point when a Win32 exception occurs, but compiling with /EHa is too burdensome since it will cause uses of catch(…) to catch Win32 exceptions. I had turned to setjmp/longjmp hoping to solve the problem that way (I don’t care about C++ stack objects being destroyed when returning to the previous execution point). Unfortunately, I’m probably going to have to implement my own setjmp/longjmp routines to do this safely without performing C++ stack unwinding.

    Ideally, Microsoft would provide versions of setjmp/longjmp that do not attempt to perform C++ stack unwinding – similar to how the Single UNIX Spec provides sigsetjmp/siglongjmp to handle asynchronous signals.

  14. This is one of the most informative articles I have seen on Exception Handling. It is definitly a must read for anyone who wants to know how to handle a Win32 exception in C++.

  15. Good article about EH. To avoid the side effects with /EHa, we can write code as following in VC 2005 with /EHs:
    void appGo()
    {
    try
    {
    // application run….
    }
    catch(…)
    {
    }
    }
    void main()
    {
    __try
    {
    appGo();
    }
    __except(1)
    {
    // do something
    }
    We put all application code into appGo() function. Thus __try/__except can handle the win32 exception, and all c++ exception can be hanlded in appGo().

    I dont know whether this method is practice.
    Please correct me.

  16. Thank you, from Spain!
    Sorry for my english.
    Very good article. It save me a migration VS2003 ==> VS2008.

  17. my wife wanted URL – IRS W-2 some time ago and learned about a great service that has a lot of fillable forms . If you are requiring URL – IRS W-2 also , here’s a https://goo.gl/qbe58c.

Leave a comment