try-catch 正常路径几乎零开销,真正性能瓶颈是 throw 触发的栈展开、对象析构及 catch 匹配;编译器因 try 块保守优化降低内联率与代码质量;应避免在高频路径使用,而将 try 外提至顶层并精简 catch 内容。

throw 时的开销远大于 try-catch 块本身
没抛异常时,try 和 catch 块几乎不产生运行时代价——现代编译器(如 GCC、Clang、MSVC)会把它们编译成纯跳转表或零指令开销的元数据。真正吃性能的是 throw 那一刻:栈展开(stack unwinding)要遍历调用栈、析构所有局部对象、查找匹配的 catch 块,这涉及符号表查询和内存遍历。
- 在 x86-64 Linux 上,一次
throw平均耗时约 1–10 μs(取决于栈深度和析构复杂度),是普通函数调用的百倍以上 - 如果局部对象有非平凡析构函数(比如
std::vector、std::string),析构开销会线性增长 - 启用
-fno-exceptions后,try/catch语法直接报错,但这也意味着你无法使用std::make_unique等可能抛异常的标准库函数
Release 模式下 try-catch 不拖慢正常路径,但影响内联和优化
编译器看到 try 块,会保守处理:它不能假设控制流一定顺序执行,因此可能放弃对跨 try 边界的代码做激进内联或寄存器分配。这不是运行时开销,而是生成的机器码质量下降。
- 函数体内有
try→ 编译器大概率不会将该函数内联进调用者,尤其当函数体较大时 - 哪怕
catch块为空(catch(...) {}),只要存在,就触发此限制 - 用
noexcept显式标注不抛异常的函数(如void foo() noexcept),能帮编译器恢复部分优化能力
Windows SEH 与 GCC SJLJ / DWARF 的实际差异
异常实现机制直接影响性能特征。Windows MSVC 默认用结构化异常处理(SEH),而 MinGW/GCC 在 Windows 上若选 --enable-sjlj-exceptions,会用 setjmp/longjmp 模拟,代价高得多;Linux 默认用 DWARF(.eh_frame),栈展开快但二进制体积略大。
- SJLJ 模式下,每个函数入口都插入
setjmp保存上下文,即使从不throw—— 白花函数调用开销 - DWARF 模式无运行时插入,但链接时需保留调试段,且 GDB 调试时栈回溯更准
- 检查当前模式:GCC 可查
__GXX_ABI_VERSION或编译日志里是否含sjlj;CMake 中避免set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsjlj-exceptions")
替代方案不是“不用异常”,而是“在哪用”
性能敏感路径(如游戏主循环、音频 DSP、高频网络包解析)不该靠 try 拦截逻辑错误;但资源获取失败(std::ifstream 构造、new 失败)仍值得用异常,因为这些本就是低频、不可预测的边界情况。
立即学习“C++免费学习笔记(深入)”;
- 用返回码(
std::expected、absl::Status)替代异常,适合已知可预期的错误分支(如协议解析失败) - 把
try尽量外提——比如在帧更新顶层捕获,而不是每帧内 1000 次循环里都套一层 -
catch块里别做耗时操作(如格式化字符串、磁盘写入),否则掩盖了throw本身的延迟
try 关键字本身,而是你把它放在了不该放的地方,或者在 catch 里干了比 throw 还重的事。










