悬垂指针 和 野指针

悬垂指针和野指针确实容易混淆,但本质都是指向无效内存,只是产生原因不同。两种指针的共同点都是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是野指针

检测和预防

检测工具:

  1. Address Sanitizer(地址消毒剂)

    • 检测悬垂指针和野指针

    • Xcode: Scheme → Diagnostics → Enable Address Sanitizer

  2. Zombie Objects(僵尸对象)

    • 主要检测悬垂指针

    • 对象释放后变成"僵尸"

  3. 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,但成因不同,预防策略也不同

相关推荐
数智工坊3 小时前
【数据结构-树与二叉树】4.5 线索二叉树
数据结构
数智工坊3 小时前
【数据结构-树与二叉树】4.3 二叉树的存储结构
数据结构
独好紫罗兰3 小时前
对python的再认识-基于数据结构进行-a004-列表-实用事务
开发语言·数据结构·python
铉铉这波能秀4 小时前
LeetCode Hot100数据结构背景知识之列表(List)Python2026新版
数据结构·leetcode·list
历程里程碑4 小时前
Linux20 : IO
linux·c语言·开发语言·数据结构·c++·算法
DeeplyMind4 小时前
第七章:数据结构大比拼
数据结构·计算机科学·少儿编程·少儿科技读物
元亓亓亓4 小时前
考研408--数据结构--day8--遍历序列&线索二叉树
数据结构·考研·408·线索二叉树
xiaoxue..4 小时前
合并两个升序链表 与 合并k个升序链表
java·javascript·数据结构·链表·面试
驭渊的小故事5 小时前
简单模板笔记
数据结构·笔记·算法
VT.馒头5 小时前
【力扣】2727. 判断对象是否为空
javascript·数据结构·算法·leetcode·职场和发展