非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可解决错误,但存在两个疑问:
- 为何不能手动释放该对象?
- 带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
相关产品推荐
相关产品推荐

