Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Better exception handling for C++

4.96/5 (42 votes)
2 Apr 2010CPOL22 min read 1   1.4K  
Custom exception handling. Fast, comprehensive, powerful
Note: Current implementation is the 2nd-generation version. Below is the summary of changes from the previous (1st-generation) implementation:
  • Nested exception handling policy changed.
  • Controlled termination triggered in case a nested exception is raised from within the core exception handling code. It's a sign of an unrecoverable corruption (such as stack memory overwritten).
  • More performance squeezed.
  • Supporting Safeseh for implicit SEH exceptions (Please see the documentation about Safeseh for more info).

Introduction

This article offers a customized implementation of the exception handling for C++. Its key features are:

  • Very much faster.
    • 20 times faster when no debugger connected to the process (normal scenario).
    • Thousands (about 3.2K) times faster when debugger connected. Also no annoying "First-chance exception ..." debug message.
  • Handles situations when a destructor of an object throws an exception during the stack unwinding (more about this later).
  • Allowes to process exceptions before the stack unwinding (both C++ and SEH).
  • Allowes to cancel exceptions (SEH).

This implementation is suited for the Msvc (MS Visual C++) compiler. But with some efforts it may be ported to other OS and compilers. Depending on the compiler/platform there may be no need to optimize the performance. However ability to handle gracefully exceptions thrown from d'tors (destructors) and to do things "during" the exception handling before the exception is actually caught and the stack is unwound - is a great feature. And it's unfortunately missing in the native C++.

I have to mention this article: How a C++ compiler implements exception handling. It describes in-depth how the compiler implements exception handling, plus it contains a custom implementation of it. Actually some time ago it helped me to solve problems with exception handling in device driver development. However the implementation provided in that article is pretty heavy, uses a lot of STL, and could not be used as-is in the device driver code. Hence - I've made my simplified implementation of exception handling, which supported only the part that was necessary for me. Later I've completed that implementation, so that it remains lite and minimalistic, yet complete and powerful.

This article also contains some theoretical background about the exception handling and its implementation, which is necessary to understand how exactly this customized imeplementation is designed and what's so special about it.

Exception handling

Surprisingly exception handling is one of the most mystified subjects in programming overall and in C++ specifically. And it's very important to know how it works and what are its benefits and limitations.

I recommend reading the article I've mentioned at the beginning. Also I recommend reading the following article: Drivers, Exceptions and C++. It observes relation between C++ exception handling and SEH, as well as effects of setting exception handling models for compiler (synchronous, asynchronous, etc.).

Here are the key things that must be clear before we continue:

  • There're different exception handling models. All they consist of the following steps:
    1. Interruption of the normal program flow and passing control (with some parameters) to the exception handling mechanism.
    2. Search for the appropriate catch block. More precisely - negotiation with exception handling blocks until one decides to handle it.
    3. Stack unwinding procedure up to catching block. During this stage every exception handling block may execute its cleanup code.
    4. Passing control to the catch block. Back to the program.
  • Windows OS has its own exception handling mechanism (not related to C++): SEH - structured exception handling. On Msvc compiler it's accessible via __try/__except, __try/__finally blocks, and RaiseException API function.
  • C++ exception are implemented by Msvc compiler via SEH too.
    • throw statement calls a _CxxThrowException CRT function, which calls RaiseException API function with exception code and parameters specific to C++.
    • For every automatic (on stack) object with d'tor the compiler automatically generates exception record, similar to __try/__finally block.
    • For try/catch blocks compiler automatically generates exception record, similar to __try/__except block. However it conceals the "negotiation" with exception handling mechanism. Instead it automatically decides to handle or not the exception based on the C++ type match.

To make things clear let's look at the following example:

C++
void SomeFunc()
{
	// ...
	SomeClass obj1; // object with d'tor

	// ...
	// Eventually decide to throw an exception
	throw 10;
	// ...
}

void AnotherFunc()
{
	// ...
	SomeClass obj2;
	
	try
	{
		SomeClass obj3;

		// ...
		SomeFunc();
	}
	catch (int n)
	{
		// Handle the exception
	}
}

In this example the compiler will generate so-called frame handlers in both functions. In SomeFunc its purpose is to call the d'tor of obj1 if the exception escapes the function. So that during the "negotiation" stage it does nothing (refuses to catch the exception). And during the unwind phase it calls the d'tor of obj1.

The frame handler generated for AnotherFunc has more things to do. During the negotiation it checks the exception parameters. If it comes out to be a C++ exception with C++ type matching the caught one (int) - it handles the exception. The handling means several things:

  1. Unwinding the stack. During this procedure the frame handler of SomeFunc gets called to destroy obj1
  2. So-called "local" unwind. Means - objects that reside within the appropriate try block should be destroyed. In our case obj3 is destroyed. Other objects within AnotherFunc remain alive
  3. Assigning the thrown value to the appropriate variable of the catch statement
  4. Calling the catch block
  5. After the catch block returns - passing control right beyond it.

And if the exception is not caught - it propagates until another block decides to catch it. Then it'll call the frame handler of AnotherFunc to destroy its objects that are currently alive. In our case those are obj3 and obj2.

Now let's see in details the exception handling mechanism at work:

  1. For throw statement the compiler generates a call to _CxxThrowException CRT function. It gets two parameters: pointer to the thrown object and a pointer to some data structure that fully describes its type information.
  2. _CxxThrowException calls RaiseException API function with exception code and parameters such that the appropriate catch blocks recorgnizes it to be a C++ exception and its thrown type and value.
  3. RaiseException function performs a kernel-mode transaction (sort of an interrupt). That's how the OS gets notified about the exception.
  4. If there's a debugger attached to the process - it gets notified about the exception.
  5. The OS checks if this exception needs its intervention. Some exceptions (such as page fault) are handled internally by the OS and the application continues execution as if nothing happened. That is, the first exception handler is actually the OS.
  6. Obviously the OS has nothing to do with C++ exceptions. Hence it just passes control to the user-mode exception handling mechanism.
  7. The user-mode mechanism analyzes the chain of registered exception registration records. It invokes the frame handler of every such a record until one decides to handle the exception. This is the negotiation stage.
  8. The handler that decided to handle the exception performs what we already discussed: stack unwind, its local unwind, call of the catch block (with some preparations), and finally passes control right after the catch block.

Customizations

As we've seen there're several components involved in exception handling. All of them may be customized (replaced). What's important is that they are all independent and the customized implementations are compatible with the standard ones (more about this later). That is, you may customize only the components you want, to achieve the needed effect. Now let's discuss them and how they're implemented

Replacement for RaiseException

The RaiseException function is very heavy. Because it involves a kernel-mode transaction (goes into the OS internals). The cost of calling a kernel-mode function is several thousands of CPU cycles, even if that function does nothing. For comparison: calling a regular function takes just a few cycles on a modern CPU. And things get much worse when there's a debugger attached to the process: the OS immediately suspends our process and notifies the debugger that we got an exception. The debugger responds, emits the annoying "First-chance" blabla and then releases our process. The performance degradation is so catastrophic that in many situations it's simply impossible to debug programs.

Exception handling is often criticized for being heavy and slow. But this is actually a question of implementation. Let's thing again: is there a real reason for a kernel-mode transaction for exception handling? There're two official reasons:

  • Make the OS (and debugger) aware of the exception.
  • But this does more harm than good IMHO. I don't see why the debugger should be aware of every exception in the program. There's a lot of situations where exceptions are something absolutely normal, that doesn't require any attention.
  • Give the OS an opportunity to handle it.
  • This is even more ridiculous. The OS has nothing to do with software application-level exceptions. Especially it has obviously nothing to do with C++ exceptions.

Hence I decided to implement a user-mode variant of RaiseException. That's how it's declared:

C++
namespace Exc
{
	void RaiseExc(EXCEPTION_RECORD&, CONTEXT* = NULL) throw (...);
} // namespace Exc

It should be used to raise software SEH exceptions.

Pros:

  • Much better performance for explicitly raised SEH exceptions.

Replacement for _CxxThrowException

The next logical step is to use the Exc::RaiseExc for C++ exceptions. As we've already seen C++ exceptions are thrown via the _CxxThrowException function that is implemented in CRT. Now we'll make it throw exception via our function. This is enabled/disabled by the following:

C++
namespace Exc
{
	void SetThrowFunction(bool bSet);
} // namespace Exc

This function enables/disables at runtime our replacement for the _CxxThrowException. This is achieved by overwriting the first portion of the function body by a jmp instruction into our function. Theoretically it's possible to build the program statically linked to our function instead of the standard _CxxThrowException, but this method requires a lot of linker adjustments. Plus this can create a mess when used across EXE/DLL boundarios (more about this later).

Pros:

  • Much better performance for C++ exceptions.

Replacement for frame handlers

Frame handler is a function that gets called for every function that needs exception handling. It's implemented in CRT and is called _CxxFrameHandler3. Among other parameters it receives a so-called frame information, which contains all the information about its try/catch blocks and the objects with d'tors.

One of the known problems of C++ is exception thrown from within a d'tor during the exception handling. But there're actually more problems with this. If you throw a C++ object (rather than a simple ordinaty type) - the exception handling mechanism creates a copy of this object via its copy-c'tor, and at the end of the process the d'tor of this temporary object is called as well. Moreover, if you catch such an object in the catch statement by value rather than reference - another copy-c'tor is called. And all those steps (including d'tors during unwinding) must be error-free. If any of the above throws an exception - the CRT implementation of the frame handler immediately calls terminate function, and the game is over.

Worth to note here that this problem is C++ - specific. In SEH there's absolutely no problem if during exception handling another exception is raised. In such a case the new exception is handled recursively by the same mechanism. Moreover, in SEH every exception handler sees the chain of all the exceptions raised yet not handled so far. This is called nested exceptions.

In C++ however there seems to de a design problem with this. It originates to the fact IMHO that exception handling in C++ is too much concealed. You just throw an exception and ... oops! You've already caught it! And yes, the d'tors of your objects are already called. But wait, if d'tors are called - this means that my program is actually working. And, like every piece of code, it has some reasonable opportunity of failure.

Well, it's argued that d'tors must never fail. This makes sense: d'tors are often used to perform sorts of a cleanup. And if you have a cleanup problem - this problem is actually non-recoberable. That is, it's legitime if you can't open a file for example, but if you can't close a valid file handle - there is a much deeper problem in the API. But on the other hand d'tors are not always used for cleanup only. Here we obviously have a problem. Also, as we've already said, there's also a problem with copy c'tors during exception handling, which have a legitime opportunity of failure.

Nevertheless, I've created another implementation of the frame handler that handles this situation. If an exception is raised during the exception handling (in either d'tor or copy c'tor) - exception handling mechanism is activated recursively. At this stage the new exception "joins" the list of currently-being-handled exceptions, and we proceed with respect to all the exceptions, until they're all handled.

Now, here there's some degree of freedom about the order in which exceptions should be handled. Previous (1st-generation) version handled exceptions in the LIFO (last in first out) order. In simple words - recursively. The drawback of that approach was that it could allow exceptions excape their target catch blocks, where they were preceeded by those exceptions that had to be handled in outer catch blocks. Let's look at the following example:

C++
try {
	try {
	
		struct X {
			~X() { throw A(); }
		} x;
	
		throw B();
	catch (B&) {
	}
} catch (A&) {
}

Here catch block for B preceeds that for A. At runtime B is thrown. But then before it's caught A is thrown as well. In this case the 1st-generation implementation would handle A first, which unwinds the stack up to the catch block of A. Then we return to handling B, but it's now unstoppable.

The question is what should be the correct behavior in such a case. Is it ok to "swap" the order of exceptions to prevent their escaping? I took some time thinking about this, and my conclusion was that this is indeed the correct thing to do. You may think of an exception as a way of abortion of some portion of the code, which ends exactly at the appropriate catch block. If you have several error conditions simultaneously each of which demands abortion up to some point - obviously you should not abort beyond the most outer of them.

Finally I've decided to rewrite the nested exception hanlind behavior, which led to the 2nd-generation exception handling mechanism. When nested exceptions occur they're handled such that every catch block attempts to catch any of the active exception. Moreover, if the same catch block can catch several exceptions - that's what it will do, and the code within it will be invoked several times. In conclusion the policy regarding the nested exceptions is the following:

  • Every catch block may catch any of the currently active exceptions.
  • The same catch block may catch more than one exception.
  • catch block may be called more than once.
  • After the catch block is invoked the control is not guaranteed to be passed right after it.
  • All the d'tors are always called once.
  • Exception handling proceeds until all the exceptions are handled. At the end the control is passed after the last (most outer) catch block.
  • If an exception can't be handled UnhandledExceptionFilter is called.

Usually you may assume that every your catch block is called no more than once. Also you may assume that after the catch block is executed you definitly get control after it. But if nested exceptions occur this rule may break. So that my customized frame handler gives you an opportunity to handle nested exceptions gracefully, but it's your duty to make sure this is indeed so.

To replace the standard CRT frame handler the following function should be called:

C++
namespace Exc
{
	void SetFrameHandler(bool bSet);
} // namespace Exc

This function enables/disables at runtime our replacement for _CxxFrameHandler3. This is achieved by the same method we've already used: overwriting the first portion of the function body by a jmp instruction into our function.

Pros:

  • Handles situations with exception during the exception handling.
  • Significantly faster than the standard imeplementation.

Exception monitoring

One of the features available in SEH but still missing in our implementation is ability to do explicit processing of the exception during the negotiation stage, and optionally cancel it. Note: you actually get an opportunity to do things before the unwinding even begins. Hence the whole stack is still valid, and it contains valuable context information. This is extremely valuable for debugging, error/crash handling, and even for logging. For more information about this I recommend reading this article: Superior logging design. Fast but comprehensive.

This functionality can be achieved easily by using pure SEH. You may wrap some code block by __try/__except block and you'll have an opportunity to analyze exceptions (and possibly do some extra processing) within the __except statement. But here comes another problem: it comes out that you can't use SEH blocks in any function that has its automatically-generated exception handling records. Means - any function that has either a try/catch block or an object with d'tor. One of the ways to overcome this is to split your function into several ones, so that we won't mix SEH and C++ exception handling in one function. This however is pretty annoying. To make life easier I've decided to give a C++ - -wrapped possibility to achieve this. Let's look at the following class declarartion:

C++
namespace Exc
{
	class Monitor  {
		// ...
	public:

		Monitor();

		virtual bool Handle(EXCEPTION_RECORD* pExc, CONTEXT* pCpuCtx) { return false; }
		virtual void AbnormalExit() {}
	};
} // namespace Exc

Override the Handle function in an inherited class, and you'll get called during the negotiation. If you return true - means you've handled the exception and the execution should continue from the point where the exception has been raised (equivalent to EXCEPTION_CONTINUE_EXECUTION in SEH). You may also override AbnormalExit to get notified when the scope is exited upon exception. (In contrast regular d'tor called in both normal and abnormal scope exit). You must however obey the following rules with using this technics:

  • Always create this object on stack only (declare as an automatic variable). Never create this object dynamically (via new operator or something similar). Also don't do any sophisticated adjustments to its lifetime. If you break this rule - exception registration record chain may get corrupted, and the program will crash.
  • Don't try to cancel exceptions that have the EXCEPTION_NONCONTINUABLE flag on.
  • In particular C++ exceptions are always non-continuable, and you should not try to make them continuable. This is because the compiler assumes the execution may never continue beyond the throw statement, and it may not generate valid code after it.

Actually I had another idea about exception monitoring in a C++ way - without using __try/__except directly. I thought about invoking the code inside the catch block before the stack unwinding for some specific thrown types. I declared some abstract class, and every exception of this type (and any C++ class derived from it) is actually caught before the stack unwinding. By such you could use exceptions of those types to be able to do more inside the catch block. For instance:

C++
// Declare the exception type that is processed before the stack unwinding
struct MyExc :public Exc::ProcessInline {
	// some parameters
};

try
{
	// ...
	MyExc exc;
	throw exc;
	
}
catch (MyExc& exc)
{
	// The stack is still alive here!
}

Unfortunately this perverted trick has failed for reasons independent of me. It worked well in debug, but crashed in release build. It came out the compiler assumes that inside the catch block all the objects instantiated within the try block are already dead, and their memory may be reused. Hence for objects declared within the catch block it generated code that overwrites the memory of those declared in the try block. I had even more fanatic ideas with creating a copy of the stack memory of the appropriate try block before invoking the appripriate catch, but then there would be problems if the catch block rethrows an exception, and so on. At the end, with heavy heart, I gave up. And then I've created the Monitor class that does the trick.

Constraints

The first concern is with using our exception handling accross EXE/DLL boundaries. There's no problem if you use DLL version of CRT (in all your DLLs and the EXE), and the global initialization of our exception handling resides in the EXE only, so that all other DLLs have no direct connection to the customized exception handling. In this case we replace the CRT version of exception handling, and all the modules automatically use it. If however some modules use statically-linked CRT libraries - their exception handling will remain intact.

Another conern is related to the fact that our implementation uses TLS (per-thread variables) to keep some information about currently being processed exception. This is necessary to correctly implement "rethrow" (throw; statement) and nested exception handling. The problem is that CRT implementation also has this sort of a per-thread exception handling information (though without support for nested exceptions), which is not related to ours. Moreover, when CRT is used as a DLL this information is inaccessible to the EXE (internal CRT functions that access it are not exported). When we handle an exception the CRT isn't aware of that, and vice versa. This has two implications.

One of the implications of this is that std::uncaught_exception() will not see exception that we handle. To overcome this I've added the following functions:

C++
namespace Exc
{
	// Replaces std::uncaught_exception
	void SetUncaughtExc(bool bSet);

	// Returns the currently processed exception
	// In contrast to std::uncaught_exception this function not only tells if
	// there's currently exception being processed, but also what it is
	EXCEPTION_RECORD* GetCurrentExc();
	
} // namespace Exc

There's also one more little problem that can arise in very special conditions. If there's a C++ exception raised by our mechanism, during which an implicit SEH exception occurs (such as access violation) - the new exception is not immediately "chained" to the previous one, so that at the beginning it doesn't contain the nested exception information. In such a case when our frame handler is being invoked for the first time - it sees that the new exception is not chained yet, and immediately chains it, so that there's no problem for all the consequent handlers. If however the first exception handler happens to be the SEH one (with __try/__except block) - it won't see the nested exception at the beginning. That is:

C++
void Func()
{
	__try {
		__try {
			// Throw an exception via our mechanism: either a C++ exception or
			// SEH raised explicitly by Exc::RaiseExc
			throw 5; // C++ exception
			
		} __finally {
			*((int*) NULL)++; // access violation, leads to an implicit nested exception
		}
	} __except (AnalyzeExc(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) {
	}
}

void AnalyzeExc(EXCEPTION_POINTERS* pExc)
{
	// Here we'll see only the access violation exception.
	// No nested exception information.
}

Well, this problem is very specific IMHO. Anyway nested exceptions are unfortunately ignored often, the even fact we take them into account is something unusual. To solve this problem either don't use SEH exception blocks directly at least where you raise exceptions via our mechanism (the whole idea is to get rid of using SEH directly), or when analyzing the exception you may also call Exc::GetCurrentExc to see if there're currently more exceptions being processed. Theoretically it's possible to replace the CRT code that support the SEH exception blocks (_except_handler4), but this is too much effort IMHO for something too unusual.

Another problem is that my exception handling mechanism currently doesn't support so-called vectored exception handling. It's an extension to SEH introduced in Windows XP. In addition to SEH it allowes to register global scope-independent handlers that automatically see every exception. The reason I didn't implement support for this is that I haven't discovered yet how to retrieve the table of all the registered vector handlers (there's no official API that does this). Plus this is something very specific, I doubt if it's actually used besides some very special conditions. So that I think there's no problem with this. And if however you insist on keeping vectored exception handlers - you should use the native RaiseException function rather than our fast Exc::RaiseExc. And if you want them to work for C++ exceptions as well (although I don't see any reason for this) - don't replace the _CxxThrowException.

Conclusion

C++ is often criticized, but I believe it's a great programming language after all. If you ask me what are the major differences between C and C++ I'd say the following:

  1. Cosmetic differences (member functions, inheritance, overloading, operators, templates, references, etc.).
  2. Built-in polymorphism support: virtual functions, and some extra features (RTTI, virtual inheritance).
  3. Destructors.
  4. Exceptions.

The first category consists of many things that make writing code in C++ easier, however they don't bring anything new on the machine level. They just allow to achieve the same effect you'd have in C, with much shorter and cleaner code. The second category makes the compiler generate code to support polymorphism automatically, however C programmers could use polymorphism too: in old good times C programmers manually constructed tables of pointers to functions to achieve the needed effect at runtime. The remaining two categories: destructors and exceptions. And those are IMHO the greatest features of C++ compared to C. And, as we've seen, they're not actually two distnict features, there's a very tight connection between them.

Some criticize C++ for things that it doesn't do. Such as no reflections, lack of built-in complex types, and so on. But I believe such a criticism is not justified. C++ grew up from C, which is the most minimalistic assembler-independent programming language. And what makes C++ so great is that it offers much more than C, without compromising the minimalism and efficiency of plain C, when used correctly. On the other hand I would criticize C++ for some things that it does, when it does them wrong way. And exception handling is unfortunately a perfect example of this. IMHO it's not designed well, the even fact it defines exceptions thrown is some situations as undefined behavior in which the program should be terminated immediately - is a shame. Plus it's implemented barbarically in CRT for Msvc.

Our customized exception handling fixes those imperfections. Now we have a well designed handling of all the exceptions in all possible situations. Plus we fix the ridiculous performance issue. Exception handling is no more that heavy. From my experiments in the simplest exception handling scenario like this:

C++
try {
	throw 1;
} catch (int) {
}

The complexity of the exception handling here is comparable with several calls of strlen("Hello, World"). For comparison it's much lighter than heap operations (malloc and free).

One point that is still missing is a language-integrated ability to analyze exceptions during the negotiation stage. My attempt to call catch blocks before the stack unwinding has failed, so the only thing left is either to use the Exc::Monitor helper class for this, or just use plain SEH. There seems to be no better solution within the standard C++ limitations.

The implementation of the exception handling is about 900 code lines, which is not that much. There're some non-trivial things there. They can be classified into the following categories:

  • Need to deal with SEH and built-in C++ types, such as type information of the object thrown and catch blocks, frame info and etc.
  • Some code written in assembler.
  • Non-trivial program flow. Some functions instead of a conditional return pass control to other code blocks.
  • This also includes the fact that during exception handling new exceptions may occur, plus CRT-based exception handlers may be involved. Actually in order to guarantee correct behavior in all those situations additional exception handlers are used internally during the exception handling.

Nevertheless the code is somewhat readable IMHO, and with some efforts it can be customized further.

I'll appreciate comments. New ideas and criticism are welcome.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)