异常处理期间的类型解析
Type resolution during exception handling
当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)
因为它能够处理1
int
类型化对象。
我不明白的是,如何静态解决?还是动态解决?类型信息是否在异常的情况下传播?
对于大多数事情,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 是(可能符合 cv 条件的)U 或 const U& (自 C++14 起),U 是指向成员(自 C++17 起)类型的指针或指针,
T 是对 E 的明确公共基类的引用
E 也是指向成员(自 C++17 起)类型的指针或指针,可通过一个或多个隐式转换为 U
- 标准指针转换,而不是到私有、受保护或不明确的基类
- 资格转换
- 函数指针转换 (自C++17起)
T 是指针或- 指向成员的指针或对常量指针的引用(自 C++14 起),而 E 是
std::nullptr_t
。
此外,如果存在包罗万象条款(catch (...)
),则如果上述任何几点不适用,则采取该条款。如果没有合适的catch
子句,则异常将继续沿调用堆栈向上移动。
正如一些答案和注释所述,编译器确实为异常类型生成运行时类型信息 RTTI,异常调度程序使用这些异常类型将正确的处理程序与异常对象匹配。
正如你从我的例子的修改版本中看到的:
https://godbolt.org/g/dyheBZ
编译器 (gcc) 在第.rodata
节中为throw
或catch
语句中使用的对象生成一个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
- 为什么我应该在异常处理中使用std::cerr而不是std::cout
- 当我使用 C++ 中的 C# dll 来使用 Selenium 时,存在异常处理问题
- C++在一个映射中存储不同的指针类型(并处理销毁)
- Firebase C++VS2018 SDL2-在Firebase::app::create(..)上执行异常处理
- 使用 stoi 功能进行异常处理
- 子系统中的异常处理:本机
- 与异常处理程序中的操作员<<不匹配
- 数组 C++ 上的异常处理程序
- 我可以让返回类型自动处理具有相同签名但捕获不同内容的 lambda 吗?
- 异常处理:如果用户输入不是三个特定字符之一
- C++ 异常处理错误输出
- 视觉 std::矢量无异常:警告 C4530:使用了C++异常处理程序,但未启用展开语义.指定 /EHsc
- C++交换机状态异常处理
- 在字符串类上的成员函数和out_of_range异常处理
- 奇怪的消息 (_Base_bitset::_M_do_to_ulong) 从溢出异常处理程序中打印出来
- 异常处理期间的类型解析
- 类型铸造在C 和异常处理中
- 正在重新处理C++或C++11中的类型异常
- c++中的异常处理从来没有捕捉到基本类型的异常,总是导致分段错误
- 类型转换和异常处理