块捕获对象的过度释放问题; 保持计数从+2直接跳到0!

Nic*_*son 8 objective-c instruments nszombie grand-central-dispatch retaincount

我对偶然发生的碰撞感到困惑,根据Zombies乐器的说法,这是由于一些字典值的过度释放造成的.当我查看Instruments中这些过度释放的对象之一的对象历史时,我发现它的保留计数在一个阶段从+2直接下降到0.(看一下帖子末尾的截图).我不清楚这是怎么可能的.

我应该说在使用Instruments进行分析时我只看到这种崩溃,所以我认为它可能是一个Apple漏洞,但是假设它是导航错误可能更安全,而Instruments只是暴露出来.

无论如何,我正在构建一个包含一些Core Foundation对象(CFStrings和CFNumbers)的CFDictionary,然后我将它转换为NSDictionary*并将其传递给Objective-C方法.我的代码的简化版本如下:

// creates a CFDictionary containing some CFStrings and CFNumbers
void doStuff() 
{
    CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes();

    dispatch_async(myQueue, ^{
        [someObject receiveDictionary:(NSDictionary*)myDict];
        CFRelease(myDict);  // this line causes a crash. The Zombies instrument
                            // claims a CFString value contained in this
                            // dictionary has already been freed.
    });
}

// ...

- (void)receiveDictionary:(NSDictionary*)dict
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSString* str1 = [dict objectForKey:@"key1"];
    NSString* str2 = [dict objectForKey:@"key2"];
    NSNumber* num1 = [dict objectForKey:@"key3"];

    dispatch_async(myOtherQueue, ^{
        [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1];
    });

    [pool drain];
}
Run Code Online (Sandbox Code Playgroud)

我曾经想过str1,str2并且num1会被视为Objective-C对象,因此当-receiveDictionary:通过dispatch_async调用复制块in时会被捕获并自动保留,并在释放该块时释放.实际上,这些变量确实似乎被块捕获并保留.但是,检查Instruments中过度释放的CFString的对象历史记录,我可以看到在复制块时它的引用计数正在递增.令人困惑的是,当一个块被释放时,它的保留计数从+2直接下降到0(参见帖子末尾的截图); 我不知道如何从堆栈跟踪告诉它是哪个块.当CFRelease在块中的字典上调用时doStuff(),它的一些值已经被释放,程序崩溃了.

那么额外的发布电话来自哪里?如仪器所示,对象的保留计数如何从+2直接下降到0?

一时兴起,我强迫第二个块保留整个字典,如下所示:

dispatch_async(myOtherQueue, ^{
    [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1];
    [dict self];
});
Run Code Online (Sandbox Code Playgroud)

这似乎使崩溃消失了; 仪器至少停止报告僵尸.我不能为我的生活看到为什么这样有效; 当然我只是为了确保该块保留我感兴趣的字典值,而不是整个字典.发生什么了?


Instruments列出了僵尸CFString的以下对象历史记录,其中包含对象的保留计数.我已经为有趣的事件添加了截图.

#0 +1创建CFString
#1 + 2 CFString添加到字典
#2 + 1 CFString发布
#3 +2 -receiveDictionary:复制块时保留CFString
#4 + 0 什么...?对象的保留计数从+2直接下降到0!
#5 -1 CFDictionary被释放,导致崩溃

Nic*_*son 0

最终发现了错误 \xe2\x80\x94 ,事实证明这根本不是僵尸问题,而是解码 Base64 数据的例程中不相关的内存损坏问题。与保留/释放、块或 GCD 无关。叹。

\n\n

事后看来,这一点应该更加明显。事实上,在 Instruments 报告过度释放的对象后不久,我的程序就崩溃了,这应该是一个线索 \xe2\x80\x94 如果它实际上是一个僵尸问题,你就不会预料到会崩溃。(我认为?)保留计数从 +2 跳到 0 可能也表明了除了简单的过度释放之外的其他原因。

\n\n

那么我学到了什么?

\n\n
    \n
  • 未经彻底检查,请勿复制粘贴代码。所有 Base64 转换例程的创建方式并不相同。(具体来说,realloc不使用其返回值进行调用是错误的,错误错误!遗憾的是静态分析器没有标记这一点。)
  • \n
  • 不要完全依赖 Instruments\xe2\x80\x94 其他工具(例如 Valgrind)可能很有用。在这种情况下,Valgrind 给了我更准确的信息。
  • \n
\n