tch块之前),也需要消除掉它(在调用catch块之后),所以编译器在这个结构中同时提供了异常的拷贝构造函数、大小和析构函数的信息。
在catch块的参数是基类,而异常是派生类时,异常处理程序也应该调用catch块。然而,这种情况下,比较它们的type_info绝对是不相等,因为它们本来就不是相同的类型。而且,type_info类也没有提供任何其他函数或运算符来指出一个类是另一个类的基类。但异常处理程序还必须得去调用catch块!为了解决这个问题,编译器只能为处理程序提供更多的信息:如果异常是派生类,那么etypeinfo_table(通过excpt_info访问)将包含多个指向etype_info(扩展了type_info,这个名字是我启的)的指针,它们分别指向了各个基类的etype_info。这样,处理程序就可以把catch块的参数和所有这些type_info比较,只要有一个相同,就调用catch块。
在结束这一部分之前,还有最后一个问题:异常处理程序是怎么知道异常和excpt_info结构的?下面我就要回答这个问题。
VC++会把throw语句翻译成下面的样子:
//throw E(); //编译器会为E生成excpt_info结构 E e = E(); //在栈上创建异常 _CxxThrowException(&e, E_EXCPT_INFO_ADDR); __CxxThrowException会把控制权连带它的两个参数都交给操作系统(控制权转移是通过软件中断实现的,请参见RaiseException)。而操作系统,在为调用异常回调函数做准备时,会把这两个参数打包到一个_EXCEPTION_RECORD结构中。接着,它从EXCEPTION_REGISTRATION链表的头结点(由FS:[0]指向)开始,依次调用各节点的异常处理程序。而且,指向当前EXCEPTION_REGISTRATION结构的指针也会作为异常处理程序的第二个参数出现。前面已经说过,VC++中的每个函数都在栈上创建并注册了EXCEPTION_REGISTRATION结构。所以传递这个参数可以让处理程序知道很多重要信息,比如说:EXCEPTION_REGISTRATION的id字段(用于查找catch块)、函数的栈桢(用于清理栈桢)和EXCEPTION_REGISTRATION结点在异常链表中的位置(用于堆栈展开)等。第一个参数是指向_EXCEPTION_RECORD结构的指针,通过它可以找到异常和它的excpt_info结构。下面是excpt.h中定义的异常回调函数的原型: EXCEPTION_DISPOSITION (*handler)( _EXCEPTION_RECORD* ExcRecord, void* EstablisherFrame, _CONTEXT *ContextRecord, void* DispatcherContext); 后两个参数和我们的讨论关系不大。函数的返回值是一个枚举类型(也在excpt.h中定义),我前面已经说过,如果处理程序找不到catch块,它就会向系统返回ExceptionContinueSearch,对本文而言,我们只要知道这一个返回值就行了。_EXCEPTION_RECORD结构是在winnt.h中定义的: struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; _EXCEPTION_RECORD* ExcRecord; PVOID ExceptionAddress; DWORD NumberParameters; DWORD ExceptionInformation[15]; }EXCEPTION_RECORD; ExceptionInformation数组中元素的个数和类型取决于ExceptionCode字段。如果是C++异常(异常代码是0xe06d7363,源于throw语句),那么数组中将包含指向异常和excpt_info结构的指针;如果是其他异常,那数组中基本上就不会有什么内容,这些异常包括除零溢出、访问违例等,你可以在winnt.h中找到它们的异常代码。 ExceptionFlags字段用于告诉异常处理程序应该采取什么操作。如果它是EH_UNWINDING(见Except.inc),那是说堆栈正在展开,这时,处理程序要清理栈桢,然后返回。否则处理程序应该在函数中查找catch块并调用它。清理栈桢意味着必须找到异常发生时生存在栈桢上的所有局部对象,并调用其析构函数,下一节我们将就此进行详细讨论。
清理栈桢
C++标准明确指 |