悬垂指针和野指针确实容易混淆,但本质都是指向无效内存,只是产生原因不同。两种指针的共同点都是EXC_BAD_ACCESS的常见原因,但预防方法不同。悬垂指针更多涉及对象生命周期管理,野指针更多是初始化问题;
悬垂指针(Dangling Pointer)和野指针(Wild Pointer)都是无效指针,但产生原因和场景不同:
核心区别
| 特征 | 悬垂指针(Dangling Pointer) | 野指针(Wild Pointer) |
|---|---|---|
| 定义 | 指向已被释放的内存 | 指向未初始化 或随机的内存 |
| 产生时机 | 对象释放后 | 指针声明后 、初始化前 |
| 内存状态 | 曾经有效,现在无效 | 从未有效分配 |
| 常见场景 | 对象释放后未置空 | 未初始化的局部指针变量 |
详细说明
1. 悬垂指针(Dangling Pointer)
// Objective-C 示例
NSString * __strong str = @"Hello";
NSString * __unsafe_unretained danglingPtr = str; // 不安全引用
str = nil; // 对象被释放
// 此时 danglingPtr 成为悬垂指针
// NSLog(@"%@", danglingPtr); // 可能崩溃:EXC_BAD_ACCESS
// Swift 示例(使用 unowned 可能产生类似效果)
class MyClass {
func doSomething() { print("Doing") }
}
var obj: MyClass? = MyClass()
unowned let danglingRef = obj! // 无主引用
obj = nil // 对象释放
// danglingRef 现在是悬垂引用
// danglingRef.doSomething() // 崩溃!
常见场景:
-
对象释放后,指针未置为
nil -
使用
__unsafe_unretained(OC)或unowned(Swift) -
C/C++ 中:局部变量离开作用域后
-
多线程中一个线程释放内存,另一线程仍在访问
2. 野指针(Wild Pointer)
// C/C++ 典型示例
void example() {
int *wildPointer; // 声明但未初始化 → 野指针
// int value = *wildPointer; // 读取随机内存,可能崩溃
int *anotherPtr = malloc(sizeof(int));
free(anotherPtr); // 释放内存
anotherPtr = NULL; // ✅ 好习惯:释放后立即置NULL
}
常见场景:
-
声明指针变量但未初始化
-
指向已释放内存且未置空
-
指针计算错误,指向非法地址
-
栈变量地址被返回(指向已销毁的栈内存)
iOS/macOS 中的特殊表现
悬垂指针常见模式:
OC
// 1. 代理模式中的悬垂指针
@property (nonatomic, weak) id<MyDelegate> delegate; // ✅ 应该用weak
@property (nonatomic, assign) id<MyDelegate> delegate; // ❌ 可能成为悬垂指针
// 2. Block 中的 self 捕获
__block MyClass *blockSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// 如果 self 在 block 执行前被释放,blockSelf 成为悬垂指针
[blockSelf doSomething];
});
// 3. 通知中心未移除观察者(iOS 9 前)
野指针在 iOS 中的体现:
// Objective-C 野指针(较少见,因为OC默认初始化nil)
MyClass *wildObj; // 自动为nil,不是野指针
// 但C指针仍然危险:
CFArrayRef cfArray;
// CFArrayGetCount(cfArray); // ❌ cfArray是野指针
检测和预防
检测工具:
-
Address Sanitizer(地址消毒剂)
-
检测悬垂指针和野指针
-
Xcode: Scheme → Diagnostics → Enable Address Sanitizer
-
-
Zombie Objects(僵尸对象)
-
主要检测悬垂指针
-
对象释放后变成"僵尸"
-
-
Malloc Scribble/Guard Edges
// 悬垂指针预防:
// 1. 使用 weak 引用
weak var safeRef: MyClass?// 2. 释放后立即置nil
var resource: NSData? = NSData()
resource = nil // 明确释放// 3. 避免 unowned,除非生命周期确定
// unowned let ref = object // 谨慎使用// 野指针预防:
// 1. 始终初始化指针
var pointer: UnsafeMutablePointer<Int>? = nil// 2. 使用可选类型(Optional)
var safePointer: MyClass? // 自动为nil,安全// 3. C指针:释放后置NULL
free(cPtr)
cPtr = NULL
实际调试技巧
// 在发生EXC_BAD_ACCESS时:
// 1. 启用僵尸对象检测
// 2. 查看崩溃堆栈
// 3. 检查变量是否为悬垂指针
// 使用安全的指针包装
class SafePointer<T: AnyObject> {
private weak var pointer: T?
init(_ pointer: T) {
self.pointer = pointer
}
func withPointer(_ action: (T) -> Void) {
if let pointer = pointer {
action(pointer)
} else {
print("指针已释放")
}
}
}
总结:
-
悬垂指针:对象死后指针还在"缅怀"
-
野指针:指针出生时没有"户口"
-
在iOS ARC环境下,悬垂指针更常见 (尤其是错误使用
unowned或 C 指针) -
两者都会导致
EXC_BAD_ACCESS,但成因不同,预防策略也不同