8

我对我看到的偶尔崩溃感到困惑,根据 Zombies 工具,这是由某些字典值的过度释放引起的。当我查看 Instruments 中这些过度释放对象之一的对象历史记录时,我发现它的保留计数在某个阶段直接从 +2 下降到 0。(请看帖子末尾的屏幕截图)。我不清楚这怎么可能。

我应该说我只在使用 Instruments 进行分析时才看到这个崩溃,所以我想这可能是一个 Apple 错误,但假设它是 Pilot 错误可能更安全,而 Instruments 只是暴露了这个错误。

无论如何,我正在构建一个包含一些核心基础对象(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];
}

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

那么额外的发布调用是从哪里来的呢?正如 Instruments 所指出的,一个对象的保留计数如何从 +2 直接下降到 0?

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

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

这似乎使崩溃消失了;仪器至少停止报告僵尸。不过,我终其一生都无法理解为什么会这样。当然,我只是为了确保该块保留我感兴趣的字典值,而不是整个字典。发生什么了?


Instruments 列出了僵尸 CFString 的以下对象历史记录,以及对象的保留计数。我已经包含了有趣事件的屏幕截图。

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

4

3 回答 3

0

您使用什么作为您的CFDictionaryKeyCallBacks以及CFDictionaryValueCallBacks何时创建字典CreateMyDictionaryContainingCFTypes()?如果我为两者都传入 NULL,我可以轻松复制这个问题,但如果我传入&kCFTypeDictionaryKeyCallBacksand ,我将无法复制它&kCFTypeDictionaryValueCallBacks

于 2011-06-14T00:43:50.023 回答
0

终于发现了这个错误——结果证明它根本不是僵尸问题,而是解码 base64 数据的例程中不相关的内存损坏问题。与保留/释放、块或 GCD 无关。叹。

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

那么我学到了什么?

  • 不要在没有彻底检查的情况下复制粘贴代码。并非所有 base64 转换例程都是一样的。(具体来说,realloc不使用返回值调用是错误的,错误的错误!可惜静态分析器没有标记这个。)
  • 不要完全依赖 Instruments——其他工具,如 Valgrind 也很有用。在这种情况下,Valgrind 给了我更准确的信息。
于 2011-06-17T22:36:53.027 回答
-1

复制的块将隐式保留其范围内的任何 Objective-C 对象,然后在释放块时也隐式释放这些对象。

CFDictionaryRef是一种免费的桥接类型NSDictionary,就块而言,也是 Objective-C 对象。这意味着您不需要做任何额外的内存管理。

让我评论您的代码,并标记评估顺序。

void doStuff() {
    // 1. myDict must have a retainCount of 1, you named your function Create
    //    and promised so according to Core Foundation men.rules.
    CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes();

    // 2. dispatch_async will copy your block and retain myDict, since it is in 
    //    scope of the block, myDict has a retainCount of 2
    dispatch_async(myQueue, ^{
        // 4. Block is execute some time later, myDict has a retainCount of 1.
        [someObject receiveDictionary:(NSDictionary*)myDict];

        // 5. Block is done and will be released, along with scoped objects
        //    on exit, retainCount reaches 0, and myDict is released.
    });

    // 3. Release your own copy before function ends, retainCount of 1
    CFRelease(myDict);  // this line causes a crash. The Zombies instrument
}
于 2011-05-14T09:54:05.013 回答