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:
- continue in a consistent state;
- crash immediately; or
- 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.
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.
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 }
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.
Nice article.. really worth reading…thanks..
A very good and helpful article. Solved my problem, thanks for the article.
Very good (and important) information not easily found anywhere. 😉
Thanks
Hi,
As you said Win32 exceptions getting caught in catch(…) is a strange behavior, what I found out while using VC7 was that they get caught in Debug mode but cause the program to crash in release mode !!! Any insights on why this happens
Thanks
Prabhpreet, this happens because all optimisations are disabled in debug builds. So the debug build has no optimisations and the release build has the usual default optimisations. As described in the article, the different optimisation levels yield different behaviour.
Hi, its excellent article.
It helps to go in detail with exception Handling. That I like
to go at compiler level, Here this article has done.
It help me to understand EH & role of compiler.
Thanks …
nice article,
could_be_a_bunch_of_rubbish(
you always can mix old c try – except exception handling with c++ using inline asm, in this way you can guard sections of code wich can only produce win32 exceptions in a safe way and being able to get crash information. This type of exceptions have nothing to do with c++ exceptions so there is no problem.Later you could pack those inline assemblies into macros. In this way it would be very easy, i mean something like this:
_TRYEXCEPT(handler)
code
_END
);
that idea come to me from a far place on my memory so.. isnt very reliable, an intuition.. dont hit me if in fact its rubbish 🙂
anyway, I need all this things to do some stuff so i have to take a closer look at it, i will post again whenever i have more info ;). happy christmas.
Good article
Don’t know if you’ve come across this before…
It would appear that you need to register the handler for each new thread you create. I tried doing it once in main(), but my other threads still showed the default behaviour until I explicitly registered the handler for each one as it started.
A very good and helpful article. Solved my problem, thanks for the article. (c) Ashish
I think you wrong about Listing 3 in “How optimisation affects exception handling” part. In my Visual Studio 2005 both listings give same result: unhandled exception. In a real sense, this additional statement should not affect on exception handling mechanism, so it is.
Noid, this article was originally about VC++ versions 6 and 7, not VC++ 2005. You’re right that the behaviour has changd in VC++ 2005. I have updated the article appropriately. I also fixed some formatting problems, and I now mention threading as suggested by Sandy above.
Thanks for your help in making the article more useful and up-to-date.
I’ll never say anything geekier, but your article was an absolute joy to read.
It will make SEH 10 times easier in the unit testing system I’m working on. Many thanks for taking the time to put this all together.
I have one more question : In the below piece of code, though the exception is caught, the destructor for t1 is not getting called. I know using the /EHa compiler option will solve this. But I don’t want to use this as it adds a overhead and reduces the speed of the program. Is there any other solution for this?
stdio.h? printf? You’re obviously a C programmer at heart. 🙂
/EHa is the only reliable way to solve this. Anything less will not guarantee that destructors will be called in all situations.
In the unlikely event that the speed overhead actually makes any measurable difference, you can turn off /EHa for the speed-critical parts of your program. Most programs spend most of their time executing only a few small segments of code. Use a profiler to find out which segments these are, debug them until you’re sure that there won’t be any asynchronous exceptions, and then compile (only) those parts without /EHa.
“Furthermore, no information will be available about the Win32 exception.”
This is not true. There is the GetExceptionCode Macro which one can use to determine the type of exception.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/getexceptioncode.asp
Hmm. I thought that
GetExceptionCode
only works within an SEH exception handler. I would be surprised if it works inside a C++catch(...)
block — but then I have been surprised before.Yes, you are right. Sorry.
Good article. Helps me!
Please provide information about getting stack trace
Packia, as I said: “The EXCEPTION_POINTERS structure that gets passed to win32_exception::translate() contains a lot of other information. In particular, you can use it to get a stack trace…”
Try looking up EXCEPTION_POINTERS on MSDN Library.
Thanks for the information. The Microsoft documents are very misleading to an exception handling newbie.