一个异常被抛出时,就会立即引发 C++ 的异常捕获机制:

图4 C++ 异常捕获机制
在上一小节中,我们已经看到了 nStep 变量在跟踪对象构造、析构方面的作用。实际上 nStep 除了能够跟踪对象创建、销毁阶段以外,还能够标识当前执行点是否在 try 块中,以及(如果当前函数有多个 try 块的话)究竟在哪个 try 块中。这是通过在每一个 try 块的入口和出口各为 nStep 赋予一个唯一 ID 值,并确保 nStep 在对应 try 块内的变化恰在此范围之内来实现的。
在具体实现异常捕获时,首先,C++ 异常处理器检查发生异常的位置是否在当前函数的某个 try 块之内。这项工作可以通过将当前函数的 nStep 值依次在 piHandler 指向 tblTryBlocks[] 表的条目中进行范围为 [nBeginStep, nEndStep) 的比对来完成。
例如:若图4 中的 FuncB 在 nStep == 2 时发生了异常,则通过比对 FuncB 的 tblTryBlocks[] 表发现 2∈[1, 3),故该异常发生在 FuncB 内的第一个 try 块中。
其次,如果异常发生的位置在当前函数中的某个 try 块内,则尝试匹配该 tblTryBlocks[] 相应条目中的 tblCatchBlocks[] 表。tblCatchBlocks[] 表中记录了与指定 try 块配套出现的所有 catch 块相关信息,包括这个 catch 块所能捕获的异常类型及其起始地址等信息。
若找到了一个匹配的 catch 块,则复制当前异常对象到此 catch 块,然后跳转到其入口地址执行块内代码。
否则,则说明异常发生位置不在当前函数的 try 块内,或者这个 try 块中没有与当前异常相匹配的 catch 块,此时则沿着函数栈框架中 piPrev 所指地址(即:异常处理链中的上一个节点)逐级重复以上过程,直至找到一个匹配的 catch 块或到达异常处理链的首节点。对于后者,我们称为发生了未捕获的异常,对于 C++ 异常处理器而言,未捕获的异常是一个严重错误,将导致当前进程被强制结束。
注意:虽然在图4示例中的 tblTryBlocks[] 只有一个条目,这个条目中的 tblCatchBlocks[] 也只有一行。但是在实际情况中,这两个表中都允许有多条记录。意即:一个函数中可以有多个 try 块,每个 try 块后均可跟随多个与之配套的 catch 块。
注意:按照标准意义上的理解,异常时的栈回退是伴随着异常捕获过程沿着异常处理链逐层向上进行的。但是有些编译器是在先完成异常捕获后再一次性进行栈回退的。无论具体实现使用了哪种方式,除非正在开发一个内存严格受限的嵌入式应用,通常我们按照标准语意来理解都不会产生什么问题。
备注:实际上 tblCatchBlocks 中还有一些较为关键但被故意省略的字段。比如指明该 catch 块异常对象复制方式(传值(拷贝构造)或传址(引用或指针))的字段,以及在何处存放被复制的异常对象(相对于入口地址的偏移位置)等信息。
|