异常处理期间的类型解析

Type resolution during exception handling

本文关键字:类型 异常处理      更新时间:2023-10-16

C++抛出异常并且堆栈被展开时,如何选择正确的处理程序(catch子句)来处理异常?

void f1()
{
throw 1;
}
void f2()
{
try
{
f1();
}
catch(const char* e)
{
std::cout << "exc1";
}
}
...
try
{
f2();
}
catch(int& e)
{
std::cout << "exc2";
}
...

例如,此代码不出所料地打印"exc2"catch(int& e)因为它能够处理1int类型化对象。

我不明白的是,如何静态解决?还是动态解决?类型信息是否在异常的情况下传播?

对于大多数事情,C++标准没有指定实现,而是约束有效的实现。不会有一个知道具体情况的通用答案。

Itanium ABI 是一种流行的 ABI,它提供与语言无关的异常支持。在该实现中,展开 API 调用堆栈帧的个性函数,该函数接收异常上下文、异常结构和对指导捕获行为的异常处理表的引用。根据程序异常表中调用的返回地址查找个性函数;假定唯一可以启动异常的指令是调用指令。(GCC 具有允许通过启用"非调用异常"从信号处理程序中抛出的扩展。使用 Itanium ABI 的编译器提供了一个个性函数,该函数知道如何检查异常对象的运行时类型,并将该类型与异常表的元素进行比较。

还有其他方法可以做到这一点。例如,在 32 位 Windows 上,异常处理的工作原理是将堆栈上的处理程序函数设置为链表项。这些链表节点包含异常处理程序的地址,它们使用EXCEPTION_RECORD来确定从那里开始的位置。不幸的是,我自己对细节有点稀疏。

顺便说一下,安腾 ABI 并不是C++独有的。例如,在Apple平台上,Objective-C代码也使用Itanium ABI作为例外。这有一个有趣的属性,即任一语言中的 catch-all 子句将捕获来自另一种语言的异常。

你可以说"类型信息在异常的情况下传播",但从技术上讲,这不是真的。当你throw某些东西时,该对象会被复制/移动,编译器将按照它们出现的顺序查找适当的catch子句,对象将被复制/移动到:

如果满足以下任一条件,则异常为匹配项:

  • E 和 T 是同一类型(忽略 T 上的顶级 cv 限定符)

  • T 是对 (可能符合 cv 条件) E 的左值引用

  • T 是 E 的明确公共基类

  • T 是对 E 的明确公共基类的引用

  • T 是(可能符合 cv 条件的)U 或 const U& (自 C++14 起),U 是指向成员(自 C++17 起)类型的指针或指针,
  • E 也是指向成员(自 C++17 起)类型的指针或指针,可通过一个或多个隐式转换为 U

    • 标准指针转换,而不是到私有、受保护或不明确的基类
    • 资格转换
    • 函数指针转换 (自C++17起)
    • T 是指针或
    • 指向成员的指针或对常量指针的引用(自 C++14 起),而 E 是std::nullptr_t

此外,如果存在包罗万象条款(catch (...)),则如果上述任何几点不适用,则采取该条款。如果没有合适的catch子句,则异常将继续沿调用堆栈向上移动。

正如一些答案和注释所述,编译器确实为异常类型生成运行时类型信息 RTTI,异常调度程序使用这些异常类型将正确的处理程序与异常对象匹配。

正如你从我的例子的修改版本中看到的:

https://godbolt.org/g/dyheBZ

编译器 (gcc) 在第.rodata节中为throwcatch语句中使用的对象生成一个typeinfo块:

.size   typeinfo for A, 16
typeinfo for A:
.quad   vtable for __cxxabiv1::__class_type_info+16
.quad   typeinfo name for A
.weak   typeinfo name for A
.section        .rodata._ZTS1A,"aG",@progbits,typeinfo name for A,comdat
.type   typeinfo name for A, @object
.size   typeinfo name for A, 3
typeinfo name for A:
.string "1A"
.text
.type   __static_initialization_and_destruction_0(int, int), @function

throw A();语句使用此信息引发异常:

mov     edi, 1
call    __cxa_allocate_exception
mov     edx, 0
mov     esi, OFFSET FLAT:typeinfo for A
mov     rdi, rax
call    __cxa_throw