More than code

More Than Code
The efficiency of your iteration of reading, practicing and thinking decides your understanding of the world.
  1. 首页
  2. 未分类
  3. 正文

CPP异常处理

2025年9月10日 31点热度 0人点赞 0条评论

记得好久之前看过一篇异常相关的文章,讲的主要是编译器插入的桩(怎么做的RAII等)

今天看CPython的时候看到了他的异常处理机制,和cpp不太一样,就延伸问了问。

直跳方案与表驱动异常:例子与定义

  • 直跳(direct jumps + 就地清理)示例:
    • C 语言:没有语言级异常,结构化控制流编译成条件/无条件跳转;资源清理靠就地代码或“goto cleanup”惯用法。
    • 部分字节码 VM(如 Lua 系列):通过显式指令(如 close/upvalue 关闭、return)配合跳转完成离开作用域的清理;错误传播多依赖 setjmp/longjmp 风格的保护调用,未以显式“块栈”建模异常路径。
    • 简化教学语言/小型解释器:常在每个可能退出路径手写清理序列与跳转目标,换取实现简单,但字节码/控制流易膨胀。
  • 表驱动异常(table‑driven exceptions)示例:
    • JVM/Java:每个方法携带异常处理表,记录“字节码区间→处理器入口(含类型约束)”,抛出异常时查表决定落点。
    • CLR/.NET:方法元数据中包含 try/catch/finally 的处理表,运行时据此展开与进入 landing pad。
    • C++ “零开销异常”(Itanium ABI / DWARF CFI、Windows SEH):正常路径无额外成本,异常时根据编译器生成的元数据表逐帧展开并跳入相应 landing pad 执行析构与处理逻辑。
  • 什么是“表驱动异常”
    • 定义:不在正常指令流中内联所有异常路径与清理序列,而是为代码段生成一张“异常处理表/展开表”(映射:指令地址区间 → 处理器或清理 landing pad + 类型匹配/栈修复信息)。抛出异常时,运行时(或硬件/ABI 指定的展开器)查表决定如何沿调用栈逐帧展开、何处执行清理、哪条处理器可接住异常。
    • 典型特征:
    • 正常路径零/低成本(无额外分支),异常路径付费;
    • 需要编译期生成并携带元数据表,运行时具备查表与展开器;
    • 对调试/回溯有更稳定的落点(landing pad),但实现复杂度较高。
    • 与 block stack 的对比:block stack 把控制流边界显式建模在解释器层,用同一套结构处理异常与结构化跳转;表驱动异常把结构信息前移到“元数据表 + 展开器”,正常路径更干净,但对解释器/编译器协作要求更高。

CPython是block stack,就是把异常块作为一个类似函数的东西进行调用,回溯的时候跟着block stack回溯即可,执行起来比较方便。

CPP这种主要是依赖编译器做异常处理:

在 C++ 中,从 throw 到进入异常处理逻辑,其背后有一套比较复杂的 异常处理模型,主要依赖于编译器生成的元信息和运行时库的支持。整体过程可以分为以下几个阶段:


1. 抛出异常 (throw)

当执行 throw 表达式时,编译器会做两件事:

  1. 构造一个异常对象(通常存放在运行时系统管理的内存区域,比如异常对象栈)。
  2. 调用运行时库的异常分发函数(如 GCC 的 __cxa_throw 或 MSVC 的 _CxxThrowException)。

这些运行时函数会保存异常对象的信息,包括:

  • 异常类型(通过 RTTI:运行时类型信息)。
  • 指向异常对象的指针。
  • 辅助的清理函数(销毁异常对象时调用)。

2. 栈展开 (Stack Unwinding)

此时运行时库会触发 栈展开(stack unwinding),即从抛出点往调用栈上层逐帧回溯。栈展开的过程主要依赖编译器在编译时生成的 异常处理表(EH tables),这些表描述了:

  • 每个函数中可能的 try-catch 块。
  • 哪些位置需要运行清理逻辑(如局部对象析构函数)。

栈展开时会做两件事:

  1. 执行需要清理的析构函数,保证局部对象正确析构(RAII 的基础)。
  2. 检查当前栈帧是否有能匹配该异常类型的 catch 块。

这一过程在不同平台上实现不同:

  • GCC/Clang 使用 DWARF EH 表 和 libunwind。
  • MSVC 使用 SEH(结构化异常处理)表。

3. 匹配 catch 块

运行时在找到某个函数的 EH 表里有匹配 catch 的类型时:

  • 停止继续展开栈。
  • 把异常对象指针传给对应的 catch 块。

比如:

try {
    throw std::runtime_error("err");
} catch (const std::exception& e) {
    // e 就是传入的异常对象引用
}

如果没有任何 catch 匹配,栈会一直展开到最外层。如果依然无人捕获,就会调用 std::terminate(),程序结束。


4. 进入异常处理逻辑

一旦找到匹配的 catch:

  • 运行时将异常对象转换成 catch 参数所需的类型。
  • 跳转到 catch 块执行。

到此,程序就进入了异常处理逻辑。


✅ 小结

  • throw 调用运行时分发函数保存异常对象。
  • 运行时通过 异常表 + 栈展开 来寻找匹配的处理器。
  • 在展开过程中会执行析构函数,保证资源释放。
  • 找到 catch 后,跳转执行异常处理逻辑。

C++ 异常展开不是 CPU 的原生特性,而是编译器 + 运行时库 + ABI 约定共同实现的协议。


1. CPU 层面

  • CPU 根本不知道 C++ 异常这回事。
  • 对 CPU 来说,throw 就是执行了一次普通的函数调用(比如 GCC 下调用 __cxa_throw,MSVC 下调用 _CxxThrowException)。
  • CPU 提供的只是最基础的:
    • 调用栈回溯的可能性(通过栈帧布局、寄存器保存约定)。
    • 间接跳转机制(运行时库可以修改返回地址/IP,跳转到新的代码块)。
  • 所以并没有“CPU 指令支持 C++ 异常”这种东西。

2. ABI / 平台协议

真正让 unwind 能工作的是 ABI 协议,比如:

  • Itanium C++ ABI(被 GCC/Clang、libc++、libstdc++ 在 Linux/Unix 系统上采用)。
    • 定义了异常对象如何存放,如何在 .eh_frame / .gcc_except_table 中描述 call-site 对应的清理/捕获逻辑。
    • 定义了 personality function 接口(比如 __gxx_personality_v0),unwinder 调用它来问“这帧该怎么处理异常?”
  • Windows x64 ABI(MSVC/Win64 用的 SEH + __CxxFrameHandler3 协议)。
    • 用 .pdata/.xdata 来描述函数的异常和清理范围。
    • 内核和 RtlUnwindEx 负责驱动展开,调用编译器生成的 funclet 完成析构。

这些 ABI 是“软件协议”,编译器和运行时都必须遵守。


3. Unwind 的实际跳转机制

当异常真正发生时:

  1. throw → 调用运行时抛出函数,运行时启动 unwinder(比如 libunwind 或 Windows 的 RtlUnwindEx)。
  2. Unwinder 读到当前栈帧的返回地址(PC),根据 EH 表 找到“如果这里有异常,应该跳到哪个 landing pad/funclet”。
  3. 修改栈帧/寄存器状态,把程序计数器(IP/RIP)设置到 landing pad 地址。
  4. 继续执行,就好像“跳转”过去了一样。

这一步并不是 CPU 内建的“异常指令”,而是运行时库通过栈帧和寄存器操作(软件实现的 longjmp/restore context)完成的。


4. 类比

可以把它想象成:

  • setjmp/longjmp 的高级版本:CPU 不管,运行时保存/恢复上下文,程序流“跳到”另一个地方。
  • 只不过 C++ 异常展开会在跳之前,遍历栈帧、查 EH 表、调用清理函数,比单纯的 longjmp 复杂很多。

✅ 结论
C++ 异常展开的“协议”不是 CPU 硬件提供的,而是 ABI 定义的软件协议。CPU 只负责最底层的指令执行(比如能让运行时改写 PC 和栈指针)。真正的 “unwind 跳转” 是运行时库依靠编译器生成的 EH 表来决定目标地址,再通过软件方式修改寄存器/栈帧,让控制流转到对应 landing pad。


在底层的实现中,就是一个跳转,抛异常的时候跳转到特殊的处理区域,做资源的回收,以及stack unwinding。

稍微上层一点,throw调用的是libunwinder,unwinder再去查编译器生成的跳转表,跳转到异常处理逻辑中。

标签: 暂无
最后更新:2025年9月10日

sheep

think again

点赞
< 上一篇

文章评论

取消回复

COPYRIGHT © 2021 heavensheep.xyz. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS