编译器如何知道使用哪个 catch 块?

How does the compiler know which catch block to take?

假设我有以下两个文件,main.cpp:

#include <iostream>

class A {};    
void foo();

int main(void)
{
    try {
        foo();
    }
    catch(const A& e) {
        std::cout << "Caught an A." << std::endl;
    }
    return 0;
}

foo.cpp

class A {};
class B : public A {};

void foo()
{
    B b;
    throw b;
}

现在,当我分别编译这些文件中的每一个时,link 生成的目标文件和 运行 生成的可执行文件,我得到了预期的结果:

$ clang++ --std=c++14 -c main.cpp
$ clang++ --std=c++14 -c foo.cpp
$ clang++ --std=c++14 main.o foo.o
$ ./a.out 
Caught an A.

这让我大吃一惊! Class A 没有虚拟方法。因此,它不是多态的,它的实例在 运行 时不应该携带任何类型信息。 main.o 目标文件不知道抛出什么,因为实际抛出发生在 foo() 内部,其主体在单独的编译单元中定义。 foo.o 目标文件有更多信息,但同样不知道任何 catch 语句和捕获异常的预期类型。

简而言之:我看不出这两个源文件如何单独编译然后 linked 可以在没有一些 运行time 类型信息的情况下产生上述输入。单独编译的文件都不应该有足够的信息来获取正确的 catch 块。

这当然完全依赖于编译器。

所有编译器的约束是:

  • 异常的类型在您抛出时是已知的(在编译时,或者在运行时,如果您抛出一个多态对象)。
  • 适用的 catch 块(可以是多个)及其类型取决于执行路径。

这意味着必须在运行时识别类型,即使异常对象是非多态的。

实现此目的的一种简单方法是将指向 typeinfo 对象的指针与抛出的对象本身一起传递。这是 GCC 使用的方法:参见 online code,这里是为方便起见在 foo() 中的摘录:

    call    __cxa_allocate_exception
    mov     edx, 0
    mov     esi, OFFSET FLAT:typeinfo for B   ; <== !! 
    mov     rdi, rax
    call    __cxa_throw

它是 RTTI(运行-时间类型信息)和在尝试评估哪个捕获时为比较类型而生成的实现特定编码类型数据的组合块得到什么。

但简单地比较两种类型的表面价值可能不会产生正确的结果(例如在您使用基础和派生 classes 的情况下)。在 Windows & SEH 的情况下,存在一个特殊的附加扩展类型信息结构(etype_info 或类似的东西),它包含层次结构中需要遍历以确定的所有 classes潜在匹配(因为基础 class 可能是派生 class 的限制视图)。如果在遍历后找到匹配项,则调用 catch 块。

附录

异常处理需要 OS 提供的特殊运行时支持,因此只要最终结果满足标准,这种情况就会由实现定义(例如 Windows 中的结构化异常处理) .

楼上是Windows风味的要点。