悬垂指针 和 野指针

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

相关推荐
Pluto_CSND2 小时前
JSONPath解析JSON数据结构
java·数据结构·json
无限进步_2 小时前
【C语言】用队列实现栈:数据结构转换的巧妙设计
c语言·开发语言·数据结构·c++·链表·visual studio
liu****2 小时前
02_Pandas_数据结构
数据结构·python·pandas·python基础
optimistic_chen3 小时前
【Redis 系列】常用数据结构---Hash类型
linux·数据结构·redis·分布式·哈希算法
五阿哥永琪3 小时前
Redis的常用数据结构
数据结构·数据库·redis
Sheep Shaun4 小时前
STL中的map和set:红黑树的优雅应用
开发语言·数据结构·c++·后端·c#
H_BB4 小时前
leetcode160:相交链表
数据结构·算法·链表
浅川.254 小时前
STL专项:queue 队列
数据结构·stl·queue
企鹅侠客5 小时前
第06章—实战应用篇:List命令详解与实战(上)
数据结构·windows·redis·list