先说结论:
C++ 异常是一种由编译期预先设计、在运行时按表执行的非局部控制流机制,用于在控制流被中断时仍然保证对象析构与资源安全。
它在正常路径上几乎没有成本,但一旦抛出就会触发昂贵且隐式的栈展开过程,因此是否使用异常,本质上是工程取舍而非语法选择。
在 C++ 里,异常是一个被大量使用,却很少被真正理解的机制。
你可能已经知道:
- throw 会**'跳出当前函数'**
- catch 可以在更外层捕获异常
- 异常发生时,局部对象会被析构
但是,如果进一步思考:
- throw 之后,CPU 的控制流是怎么改变的?
- 栈是什么时候,以什么顺序展开的?
- 为什么在很多地方,会显示禁用异常,只用错误码?
从一个关键事实开始:异常不是普通的控制流
先看一段普通的代码:
void f() { throw std::runtime_error("error"); }
void g() { f(); }
int main() { try { g(); } catch (...) { } }
直觉上,我们认为:throw 就像一种**特殊的 'return',**一层层返回,直到遇到 catch。
但这并不准确。
从底层的视角看,异常机制的本质特征是:
异常不是'逐层返回',而是一次非局部跳转
也就是说:
- throw 发生时,并不会执行后续语句
- 当前函数不会'正常返回'
- 程序会进入一条完全不同的控制路径
这条路径的名字叫:栈展开 (stack unwinding)
栈展开是什么?
栈展开可以理解成一个三阶段过程:抛出 → 寻找处理者 → 回溯清理并跳转。
(1)抛出阶段:构造异常对象 + 进入运行时
当程序运行到:
throw std::runtime_error();


