c++oding="utf-8" ?>
catch throw命令使gdb在任意throw表达式执行时中断,精准定位异常抛出点而非捕获点,需配合-g -o0编译以保证调用栈完整可读,多线程下须手动检查各线程栈帧。

用 catch throw 捕获异常抛出点
GDB 默认不会在 C++ 异常抛出时停住,必须显式设置断点。最直接有效的方式是执行:catch throw。它会让程序在任何 throw 表达式执行的瞬间中断,此时调用栈就是异常诞生的位置,而非最终被捕获的地方。
常见错误现象:只设了普通断点(如 break main)或等异常“冒泡”到 catch 块才停,结果错过原始出错点;或者误用 catch catch(它停在 catch 块入口,对定位根因帮助很小)。
- 执行前确保程序已用
-g编译(否则看不到源码行号) - 如果程序含多线程,
catch throw仍有效,但需注意当前线程上下文 —— 异常总在抛出它的那个线程中触发 - 不支持捕获
noexcept违反导致的std::terminate(那属于未处理异常终止,不是普通throw)
为什么 catch throw 比看崩溃堆栈更准
当 C++ 异常未被捕获,最终触发 std::terminate 并 abort,GDB 加载 core 文件后看到的堆栈往往止步于 __gnu_cxx::__verbose_terminate_handler 或 raise,原始抛出位置早已被 unwind 掉。而 catch throw 在 unwind 开始前就介入,调用栈干净、完整、可读。
典型场景:一个 std::vector::at() 越界在子线程里抛出 std::out_of_range,主线程完全不知情。若只等 crash,bt 显示的全是 libstdc++ 内部函数;但用 catch throw,能立刻停在 vec.at(1) 那一行。
- 对比命令:
catch catch停在catch (const std::exception& e)这一行,对查 bug 几乎无用 -
catch throw不依赖异常是否被处理 —— 即使有 try/catch,它也优先在 throw 处中断 - VS / LLDB 用户注意:LLDB 对应命令是
break set -E c++,不是catch throw
调试前必须检查的编译选项
没有调试信息,catch throw 虽然能停住,但 bt 只显示地址和函数名(如 _ZStlsIcSt11char_traitsIcESaIcEE...),无法对应到源码行。
关键参数只有两个:-g 和 -O0。前者让 GDB 知道哪行代码对应哪个地址;后者防止编译器内联或重排,保证停住时变量值、调用关系与源码一致。
- 正确编译命令示例:
g++ -g -O0 -std=c++17 main.cpp -o main - 若用了 CMake,确认
CMAKE_BUILD_TYPE是Debug,且CMAKE_CXX_FLAGS_DEBUG包含-g -O0 - 运行
file main在 GDB 中可快速验证:输出含 “with debug_info” 才算成功
容易忽略的线程与符号问题
多线程环境下,catch throw 触发时,GDB 默认只显示当前线程栈。如果异常发生在后台线程,你可能根本没注意到它停住了 —— 终端无提示、也没自动切到对应线程。
必须手动检查:info threads 看所有线程状态,再用 thread apply all bt 扫一遍全部栈帧。你会发现,真正抛异常的那个线程,其栈顶是清晰的用户代码行,而其他线程停留在 pthread_cond_wait 或类似系统调用上。
- 若
bt显示函数名是乱码(如_ZNSs4_Rep20_S_empty_rep_storage),说明没加-C参数或符号未 demangle —— 在 GDB 中执行set print pretty on和set print demangle on可修复 - 某些发行版默认关闭 C++ 符号调试包(如 Ubuntu 的
libstdc++6-<version>-dbg</version>),bt会卡在??—— 此时需单独安装对应 deb 包
catch throw 就只是个摆设。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











