C++标准说异常是“零成本“的——读了GCC的eh_frame和unwind源码,发现不抛时确实免费,但一旦throw,你的程序要在一张隐藏的表里做一次O(n)栈展开

张开发
2026/4/11 23:38:09 15 分钟阅读

分享文章

C++标准说异常是“零成本“的——读了GCC的eh_frame和unwind源码,发现不抛时确实免费,但一旦throw,你的程序要在一张隐藏的表里做一次O(n)栈展开
你写了一行throw std::runtime_error("something went wrong")。抛出、接住,程序继续跑。干净利落。但"抛出"和"接住"之间,隔着一套你在源码里完全看不到的机制。编译器在你写的每个函数旁边偷偷生成了一张表,记着:如果异常从这个位置飞出去,哪些局部对象需要析构、哪个 PC 范围有 catch 块、怎么把寄存器恢复到上一帧的状态。这张表在不抛异常时确实零开销——没有一条额外指令被执行,没有一个寄存器被多占,甚至连一次条件判断都不会多做。但一旦你 throw 了,运行时库要在这张表里做一次线性扫描,逐帧回溯调用栈,每一帧都要解码一段 DWARF 字节码来恢复寄存器状态。调用栈越深,开销越大。三句话概括:编译时付账,运行时不抛免费,抛了很贵。这篇文章要做的事很具体:从 GCC 的源码出发,追踪一次 throw 从触发到被 catch 的完整路径,搞清楚编译器生成了什么表、运行时怎么查这张表、每一步的性能开销从哪来。不泛泛讲"异常处理是什么"。逐函数、逐数据结构地追踪libstdc++的eh_throw.cc、eh_personality.cc和libgcc的unwind-dw2.c、unwind.inc,让你知道那些"隐藏的表"长什么样、"O(n) 栈展开"具体在做什么。主要参考 GCC trunk 最新源码,涉及的关键文件:libstdc++-v3/lib

更多文章