Introduction
Before I start with my article, I would like to mention some important things. The most important fact you should always keep in mind is that this article is not (and should not be) a detailed reference on x86 exception handling, but a first introduction on it. While some things probably are not exactly represented as they are done in the real world, the article sheds some light on how it is conceptually done. If you would care about every detail and explain everything in detail, you most likely will be writing an article for years.
After seeing that a lot of people (mostly coming from Java and not knowing that the compiler error that an exception is not caught is coming from the fact that the developer stuck the possible exceptions in the function doc) think that try - catch is a concept which is completely analyzed during compile time and therefore won't have big impact at runtime, I thought I might shed some light on how the Microsoft compiler (cl.exe) acts with try, catch and throw. First off, we will have a look at how a throw statement is interpreted by the compiler. Let's have a look at the following code:
int main()
{
try
{
throw 2;
}
catch(...)
{
}
}
Throw
Throw
For now, we are only interested in the throw 2. When the compiler hits the throw statement, it actually has no clue if the exception it's now converting is handled by an exception handler (and it doesn't care). The throw statement will be converted into a call to _CxxThrowException (exported by MSVCR100.dll (or any other version)). That function is a built in function in the compiler. You can call it yourself if you like ;). The first parameter of that function is a pointer to the object thrown. Therefore it gets clear, that the code above definitely expands to the following:
int main()
{
try
{
int throwObj = 2;
throw throwObj;
}
catch(...)
{
}
}
The second parameter of _CxxThrowException holds a pointer to a _ThrowInfo object. _ThrowInfo is also a built in type of the compiler. It's a struct holding various information about the type of exception that was thrown. It looks like that:
typedef const struct _s__ThrowInfo
{
unsigned int attributes;
_PMFN pmfnUnwind;
int (__cdecl*pForwardCompat)(...);
_CatchableTypeArray *pCatachableTypeArray;
} _ThrowInfo;
Here the important thing is the _CatchableTypeArray. It holds a set of runtime type information of the types that are catchable within this throw. In our case, that's pretty simple. The only catchable type is typeid(int). Let's say you have a class derived from std::exception called my_exception. If you now throw an object of type my_exception, you will have two entries in pCatchableTypeArray. One of them is typeid(my_exception) and the other is typeid(std::exception).
The compiler now fills the _ThrowInfo object as a global variable (and all the other objects needed). In the above case, this is done in the following way:
_TypeDescriptor tDescInt = typeid(int);
_CatchableType tcatchInt =
{
0,
&tDescInt,
0,
0,
0,
0,
NULL,
};
_CatchableTypeArray tcatchArrInt =
{
1,
&tcatchInt,
};
_ThrowInfo tiMain1 =
{
0,
NULL,
NULL,
&tcatchArrInt
};
You see that that's a pretty lot of information stored just for the throw 2. So finally our above code expands to:
Read more: Codeproject