iOS 指针深度精讲:野指针、悬垂指针、空指针、僵尸指针

3 阅读5分钟

先把4 种易混指针定义、成因、区别、崩溃现象、规避方案一次性讲清,全是 iOS/OC 底层实打实考点。

一、先搞懂基础:指针本质

指针 = 内存地址变量

  • 保存的是某块内存的起始地址
  • OC 里 id、对象、基本类型指针,全是地址
  • 堆、栈、全局区都靠指针访问

内存分三块:

  1. :系统自动分配、自动回收(函数局部变量)
  2. :手动 malloc/alloc,自己管理释放
  3. 全局 / 静态区:程序生命周期一直存在

所有野指针、悬垂指针,根源都是:指针还在,指向的内存已经失效 / 被回收


二、4 种指针完整定义 + 场景 + 区别

1. 空指针 NULL /nil

定义:指针地址 = 0,不指向任何合法内存。

objc

id obj = nil;
int *p = NULL;

特点:

  • 给空指针发消息、调用方法 不会崩溃
  • 地址 0 是系统保护地址,不允许读写
  • 安全、可控,日常开发大量用

用途:

  • 初始化指针、对象置空、防野指针
  • free 后置 ptr = NULL

2. 野指针 Wild Pointer(最常见、最容易崩溃)

定义:指针不为空,指向一块未知、未申请、已释放、无权访问的随机内存地址。

核心特征:

  • 指针有值,但指向的内存非法
  • 访问直接 EXC_BAD_ACCESS 崩溃
  • 崩溃随机、偶现、难复现

产生原因:

  1. 局部对象栈内存出作用域被销毁,指针还保留旧地址
  2. free/release 后没置空,指针还指着已释放堆内存
  3. 未初始化指针,指向随机垃圾地址
  4. 数组越界、内存踩踏破坏指针指向

示例野指针:

objc

Person *p; // 未初始化,随机地址
[p sayHi]; // 野指针,直接崩溃

3. 悬垂指针 Dangling Pointer(常和野指针混淆)

定义指针本身合法,但指向的内存已经被释放 / 销毁,指针还挂着旧地址,悬空吊着。

一句话区别:

  • 野指针:一开始就乱指、随机地址
  • 悬垂指针:曾经正常指向,内存释放后变成悬空

产生场景:

  1. MRC 下 release 对象,没置 nil
  2. C 语言 free 堆内存,指针不置空
  3. 栈局部变量生命周期结束,外部还持有它的地址

示例悬垂指针:

c

运行

int *fun() {
    int a = 10;
    return &a; // a 在栈上,函数结束立刻销毁
}
// 拿到的就是 悬垂指针
int *p = fun();

悬垂指针访问,大概率也会野指针崩溃,很多公司面试会刻意区分:

野指针:随机乱指悬垂指针:曾经合法,内存已回收,指针还吊着


4. 僵尸指针 Zombie Pointer

定义:对象已经 dealloc 销毁,但系统不立即回收内存,标记为僵尸对象;指针还指向这块 “尸体内存”,就是僵尸指针。

开启僵尸模式(Xcode Zombie):

  • 对象销毁后不真的回收,标记为 NSZombie
  • 再访问会直接打印:message sent to deallocated instance
  • 专门用来排查野指针 / 悬垂指针崩溃

特点:

  • 开发调试用,线上关闭
  • 能精准定位:哪个对象、哪个方法被发消息时已销毁

三、野指针 vs 悬垂指针 极简对比

表格

类型地址来源内存状态现象
空指针地址 = 0无内存发消息不崩溃
野指针随机垃圾地址非法未知内存必崩 / 随机崩
悬垂指针曾经合法地址内存已释放回收访问就崩
僵尸指针原对象地址标记僵尸不回收调试可精准报错

记忆口诀:乱指叫野,释放没置空叫悬垂,销毁留尸体叫僵尸。


四、iOS 里为什么野指针崩溃难查?

  1. 内存释放后不一定立刻被覆盖,有时能正常跑,偶尔崩
  2. 栈内存复用、堆内存复用,随机性极强
  3. 多线程时序问题,必现概率低
  4. 崩溃栈不直观,看不出是哪个对象野掉

五、产生野 / 悬垂指针 常见场景(iOS 实际开发)

  1. MRC 未手动置 nil

objc

[_person release];
// 没写 _person = nil; 变成悬垂指针
  1. Block 循环引用 + 对象提前释放
  2. 代理不写 weak,用了 strong控制器销毁后,代理指针变成悬垂,回调直接崩
  3. C 语言 malloc/free 不置空

c

运行

free(p);
// 不 p = NULL; 悬垂指针
  1. 栈变量地址外传、数组越界、内存踩踏

六、如何规避野指针 & 悬垂指针(工程必备)

1. OC 开发

  • 一律用 ARC,减少手动内存管理失误
  • 代理、闭包回调全部用 weak
  • 对象释放 / 不用时主动赋值 nil
  • 容器存对象避免悬空引用

2. C 内存开发

  • malloc/calloc/realloc 用完必须 free
  • free 后立刻手动置空:p = NULL
  • 绝不返回栈变量地址

3. 调试手段

  • Xcode 开启 Zombies 排查已销毁对象访问
  • 开启地址消毒、内存越界检测
  • 静态分析 Analyze 提前查出野指针风险

七、总结

  1. 空指针:地址为 0,不指向有效内存,OC 给 nil 发消息安全不崩溃。
  2. 野指针:指针指向随机非法内存地址,访问直接 EXC_BAD_ACCESS 崩溃,多因未初始化、越界、乱指。
  3. 悬垂指针:原本指向合法内存,内存释放 / 销毁后指针未置空,依旧持有旧地址,悬空无依托,访问同样会野指针崩溃。
  4. 僵尸指针:对象已销毁,内存被标记为僵尸不回收,用于调试定位野指针调用栈。核心区别:野是乱指,悬垂是释放后还吊着