野指针是未初始化、已释放未置空或指向局部变量的指针,会导致崩溃或静默破坏;应优先使用智能指针、及时置空、避免返回局部地址,并用AddressSanitizer检测。

野指针不是“偶尔出错”,而是只要存在,就随时可能在任意调用路径上触发崩溃或静默数据破坏。它不报错、不警告、不拒绝执行——直到某次内存页被重映射、某个变量被覆盖、某个调试器刚好没挂上。
未初始化指针直接解引用
这是最隐蔽也最容易复现的野指针来源:局部指针变量声明后没赋值,ptr 里存的是栈上残留的随机值,*ptr = 42 就等于往一个完全不可控的地址写入。
- 常见于函数开头只写
int* ptr;,后面跳过初始化直接传参或解引用 - C++ 不会自动初始化内置类型指针(区别于类成员或静态存储期变量)
- 编译器极少报错(除非开启
-Wuninitialized且能静态推导路径) - 示例:
int* p; printf("%d", *p);—— 行为未定义,但可能输出垃圾值、卡死、或看似正常
delete/free 后忘记置空
释放内存本身没问题,问题出在 ptr 还“活着”:它仍持有旧地址,后续任何 if (ptr) { *ptr = ... } 都会误判为合法。
- 多线程环境下尤其危险:A 线程刚
delete ptr,B 线程还没看到ptr == nullptr就去读写 - 条件分支多、作用域长的函数里,容易漏掉置空逻辑
- 不要依赖“我刚释放完,肯定没人再用”——代码演化后这个假设会失效
- 正确做法:
delete ptr; ptr = nullptr;(C++11 起推荐用nullptr而非NULL)
返回局部变量地址
函数返回时,栈帧弹出,局部变量生命周期终结。&local_var 的值虽仍可打印,但它指向的内存已归还给栈管理器,下次函数调用可能直接覆盖。
- 典型错误:
int* f() { int x = 42; return &x; }—— 编译器可能只给警告(-Wreturn-stack-address),不报错 - 更难察觉的是嵌套调用:A 函数返回局部地址,B 函数保存该指针,C 函数几层之后才解引用
- 即使“看起来能读到原值”,也只是运气好——栈空间尚未被复用,不代表安全
- 替代方案:改用
static int x(注意线程安全)、或分配堆内存(配合智能指针)
用智能指针替代裸指针管理生命周期
手动跟踪 new/delete 匹配是反模式。现代 C++ 中,90% 以上的动态内存场景应交给 std::unique_ptr 或 std::shared_ptr。
-
std::unique_ptr:独占所有权,离开作用域自动delete,不能拷贝,避免悬空风险 -
std::shared_ptr:引用计数,适合共享所有权;但要注意循环引用(用std::weak_ptr打破) - 它们无法指向栈变量,天然杜绝“返回局部地址”类错误
- 示例:
auto p = std::make_unique—— 无需(42); delete,无需置空,析构即释放
真正棘手的不是“怎么写对”,而是“怎么让旧代码不退化”:已有大量裸指针的项目,加 nullptr 置空只是止痛药;根治要靠逐步引入 RAII 和智能指针,并配合 AddressSanitizer 在 CI 中捕获越界/悬空访问——否则野指针永远躲在凌晨三点的 core dump 里等你。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











