深夜十一点还在加班的你,盯着屏幕上那片刺眼的红色日志,心里拔凉拔凉的。
本地测试跑得好好的代码,一到线上,晚高峰还没过,系统直接崩了。

Core Dump 文件倒是生成了,可你翻来覆去看了半天,愣是找不到问题出在哪儿。
很多人第一反应是测试没做到位,或者流量太大扛不住。但是,线上那些崩溃和性能雪崩,大多在写代码那会儿就已经埋下了根。
滥用原始指针
C++ 线上崩溃排名第一的,就是段错误。很多老项目里,new 和 delete 满天飞,全是裸指针在裸奔。
问题在于,一个裸指针根本不会告诉你:这货什么时候该释放、该谁来释放、是指向单个对象还是数组。这种信息缺失,时间久了必然出问题,不是内存泄漏,就是重复释放。
有些人觉得用智能指针就安全了,那可未必。如果不清楚底层原理,还是会崩。
双重释放的坑
来看这段代码,你觉得会怎样?
Widget* raw = new Widget();
std::shared_ptr<Widget> p1(raw);
std::shared_ptr<Widget> p2(raw); // 灾难开始
两个 shared_ptr 各自以为自己是唯一 owner,离开作用域时各删一次,同一块地址被 delete 两遍,程序直接崩溃。
正确姿势是用 make_shared:
auto p1 = std::make_shared<Widget>();
std::shared_ptr<Widget> p2 = p1; // 引用计数正确共享
循环引用的坑
双向引用也很容易翻车:
struct Node {
std::shared_ptr<Node> next;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->next = a; // 循环引用,内存永远不释放
这时候要把其中一端换成 weak_ptr:
struct Node {
std::weak_ptr<Node> next; // 改成weak_ptr
};
对慢 I/O 毫无防备
微服务架构下,C++ 服务免不了要调用 RPC、数据库或者缓存。很多人只考虑了调用失败这种情况,却忽略了更可怕的问题:调用变慢。
没有给外部调用设置超时时间?当下游抖动变慢时,你的工作线程就会被大量挂起等待。连接池和线程池很快被打满,本来局部的小问题,瞬间把压力传染给同层所有健康节点,整层服务直接瘫痪。
每个集成点都要设超时,同时引入熔断和降级机制。依赖不可用时,果断快速失败,别让外部故障把自己拖下水。这种对外部网络/系统依赖的管理,是稳定性的基石。
没有监控体系
新手遇到故障,第一反应是赶紧看代码、找 bug、修复。但匆忙打补丁往往会引入新问题。老手的原则永远是:先止损,再排查。
可是很多项目上线前根本没接崩溃捕获工具。一旦进程崩了,连错误调用栈都拿不到,只能对着日志瞎猜。
建议上线前把 Google Breakpad 或者 Crashpad 接入进来。这两个都是 Google 维护的崩溃报告库,能生成 minidump 文件,记录崩溃时的调用栈和内存状态,方便事后分析。
部署流程大概是:
- 编译时生成符号文件(用
dump_syms 工具)
- 程序初始化时启动 Breakpad 异常处理器
- 崩溃时自动生成
.dmp 文件
- 上传文件后用
minidump_stackwalk 解析出调用栈
另外,开发阶段编译时加上 AddressSanitizer(ASan),能帮你提前发现内存问题:
g++ -fsanitize=address -g your_code.cpp -o app
./app
ASan 会在运行时检测越界访问、释放后使用、内存泄漏等问题,输出非常详细。将这类工具集成到运维/DevOps/SRE流程中,能极大提升代码质量和线上稳定性。
很多线上崩溃,根源都藏在开发阶段的习惯和意识里。从内存管理到外部依赖处理,再到可观测性建设,每一步都需要开发者有足够的警觉。在技术社区如云栈社区与同行交流这些“踩坑”经验,往往能帮你在代码上线前,就排掉不少暗雷。