共计 1513 个字符,预计需要花费 4 分钟才能阅读完成。
1. 问题背景:为什么我的程序崩溃了?
刚学习 C ++ 时,最让人头疼的就是程序突然崩溃,屏幕上出现 Segmentation fault 或者 Stack overflow 这样的提示。这些都属于函数调用访问异常,常见的有以下几种类型:

- 段错误(Segmentation fault):
- 访问了无效的内存地址(比如空指针解引用)
-
典型场景:
int* ptr = nullptr; *ptr = 42; -
栈溢出(Stack overflow):
- 递归调用太深或局部变量占用空间过大
-
典型场景:无限递归或者定义超大数组
int arr[10000000]; -
栈损坏(Stack corruption):
- 缓冲区溢出覆盖了返回地址
- 典型场景:
char buf[4]; strcpy(buf, "hello");
2. 实战分析:用 GDB 调试崩溃程序
假设我们有以下崩溃代码:
void crash() {
int* p = nullptr;
*p = 42; // 这里会触发段错误
}
int main() {crash();
return 0;
}
调试步骤:
-
编译时加上调试信息:
g++ -g demo.cpp -o demo -
运行程序生成 core dump:
ulimit -c unlimited ./demo -
使用 GDB 分析:
gdb ./demo core
在 GDB 中输入 bt 可以看到调用栈:
#0 0x0000555555555151 in crash () at demo.cpp:3
#1 0x0000555555555166 in main () at demo.cpp:7
3. 防御性编程:比异常处理更好的选择
3.1 智能指针防内存泄漏
传统方式容易忘记释放内存:
void unsafe() {int* arr = new int[100];
// ... 如果这里抛出异常,内存就泄漏了
delete[] arr;}
改进方案:
#include <memory>
void safe() {auto arr = std::make_unique<int[]>(100); // C++14 引入
// 即使抛出异常也会自动释放
}
3.2 边界检查防越界
危险代码:
void risky(std::vector<int>& v) {v[100] = 1; // 可能越界
}
安全写法:
void safe(std::vector<int>& v) {if (v.size() > 100) {v.at(100) = 1; // at()会做边界检查}
}
4. 工具链:把问题扼杀在编译期
4.1 Clang 静态分析
安装工具:
sudo apt install clang-tools
检测代码:
clang-tidy --checks="*" demo.cpp -- -std=c++17
4.2 运行时检测
编译时加入检测选项:
g++ -fsanitize=address -g demo.cpp
运行后会给出详细错误报告:
==12345==ERROR: AddressSanitizer: heap-buffer-overflow
5. 生产环境最佳实践
- 性能敏感场景:
- 异常处理有开销,关键路径可以用错误码
-
但错误码容易被忽略,需要权衡
-
多线程注意:
- 异常不能跨线程传播
- 线程函数内要捕获所有异常
void thread_func() {
try {// 线程逻辑} catch (...) {// 记录日志}
}
6. 思考题
- 当回调函数抛出异常时,如何保证调用方的安全?
- 在实时系统中,应该完全禁用异常吗?为什么?
- 如何设计一个异常安全的资源管理类?
总结
处理函数调用异常就像系安全带——平时觉得麻烦,出事时能救命。记住三个关键点:
- 预防优于处理(多用智能指针、边界检查)
- 工具辅助(静态分析、AddressSanitizer)
- 根据场景选择策略(异常 vs 错误码)
希望这篇指南能帮你少踩一些坑。遇到奇怪的崩溃时,别急着重启电脑,先用 GDB 看看调用栈,问题往往就藏在那里。
正文完
