You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非ARC代码调用ARC编译的copyDict函数引发EXC_BAD_ACCESS问题咨询

ARC与非ARC交互的内存错误问题

代码场景

非ARC编译的测试代码:

void test(void) {
    @autoreleasepool {
        NSDictionary *dict = copyDict();
        NSLog(@"%lu", [dict retainCount]);
        [dict release];
    }
}

int main (int argc, const char * argv[])
{
    test();
}

该代码调用了ARC编译的框架函数copyDict(),实现如下:

NSDictionary *copyDict(void) {
    NSString *path = @"/var/root/dict_file";
    NSDictionary *dict = @{
        @"a":@"a"
    };
    [dict writeToFile:path atomically:YES];
    return [[NSDictionary alloc] initWithContentsOfFile:path];
}

问题现象

运行代码时,在autorelease pool释放(objc_autoreleasePoolPop)阶段触发EXC_BAD_ACCESS错误,错误位置:

for: objc_autoreleasePoolPop
->  0x104aa0058 <+88>: ldp    x29, x30, [sp, #0x20]
    0x104aa005c <+92>: add    sp, sp, #0x30
    0x104aa0060 <+96>: retab  

删除[dict release]或移除@autoreleasepool可解决错误,但存在两个疑问:

  1. 为何不能手动释放该对象?
  2. 带copy前缀的函数应返回单引用计数的对象,但打印的retainCount为2而非1,这是为何?

问题解答

1. 为何不能手动释放该对象?

核心原因是ARC与非ARC代码的内存管理规则冲突:

  • copyDict是ARC编译的C风格函数,ARC对C函数的返回对象默认采用NS_RETURNS_AUTORELEASED规则——返回的对象已经被加入autorelease pool,调用者(非ARC代码)不需要手动管理其生命周期,pool会在销毁时自动释放它。
  • 你在非ARC代码里手动调用[dict release],相当于提前把对象的引用计数减到0,对象被销毁;等到autorelease pool执行pop操作时,又会尝试释放这个已经被销毁的对象,直接触发野指针错误EXC_BAD_ACCESS。

2. 为何retainCount打印为2?

首先要明确:retainCount的数值不能完全反映对象的真实引用情况,系统内部的缓存、自动池持有等操作都会干扰计数结果,但这个场景下的数值可以拆解:

  • copyDict里用alloc创建的字典引用计数初始为1。
  • ARC为了将其作为autoreleased对象返回,会把对象加入当前的autorelease pool,而pool在持有对象时会额外做一次retain,此时对象的引用计数变为2。
  • 另外要注意:C风格函数不遵循Objective-C的方法名内存规则(比如带copy前缀的方法返回拥有权对象),ARC不会因为函数名带copy就返回需要手动释放的对象,这也是计数不符合预期的诱因之一。

额外提醒:永远不要依赖retainCount来调试内存问题,系统框架内部的黑箱操作会让这个数值失去参考意义,正确的做法是严格遵循对应编译环境的内存管理规则。

内容的提问来源于stack exchange,提问作者Yijue Xu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 20:28:11