8

Zombies インストゥルメントによると、一部のディクショナリ値の過剰リリースが原因で、時折発生するクラッシュに混乱しています。Instruments でこれらのオーバーリリースされたオブジェクトの 1 つのオブジェクト履歴を見ると、その保持カウントが 1 つの段階で +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];
}

と はObjective-C オブジェクトと見なされるため、呼び出しによってブロックがコピーされるとキャプチャされて自動的に保持str1され、そのブロックが解放されると解放されると考えていました。実際、これらの変数はブロックによってキャプチャおよび保持されているようです。しかし、Instruments でオーバーリリースされた CFString のオブジェクト履歴を調べると、ブロックがコピーされると参照カウントが増加していることがわかります。困ったことに、ブロックが解放されると保持カウントが +2 から直接 0 に低下します (記事の最後にあるスクリーンショットを参照)。スタック トレースから、これがどのブロックかを判断する方法がわかりません。ブロック内の辞書で呼び出されるまでにstr2num1-receiveDictionary:dispatch_asyncCFReleasedoStuff()、その値の一部は既に割り当てが解除されており、プログラムがクラッシュします。

では、追加のリリース コールはどこから来たのでしょうか。Instruments が示すように、オブジェクトの保持カウントが +2 から 0 に直接低下するのはなぜでしょうか?

気まぐれで、次のように、2 番目のブロックに辞書全体を強制的に保持させました。

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のブロックを-receiveDictionary:コピー
しても CFString を保持 #4 +0なに…?オブジェクトのリテイン カウントが +2 から 0 に急降下しました!
#5 -1 CFDictionary が解放され、クラッシュする

4

3 に答える 3

0

で辞書を作成するときに、何を使用していますCFDictionaryKeyCallBacksか?両方にNULLを渡すと、この問題を簡単に再現できますが、とを渡すと、この問題を再現できません。CFDictionaryValueCallBacksCreateMyDictionaryContainingCFTypes()&kCFTypeDictionaryKeyCallBacks&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 に答える