9

バグを最小限の再現可能なケースに絞り込もうとしているところ、奇妙なことがわかりました。

次のコードを検討してください。

static NSString *staticString = nil;
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    if (staticString == nil) {
        staticString = [[NSArray arrayWithObjects:@"1", @"2", @"3", nil] componentsJoinedByString:@","];
    }   

    [pool drain];

    NSLog(@"static: %@", staticString);
    return 0;
}

このコードがクラッシュすることを期待しています。代わりに、以下をログに記録します。

2011-01-18 14:41:06.311 EmptyFoundation[61419:a0f] static: static: 

ただし、次のように変更するNSLog()と:

NSLog(@"static: %s", [staticString UTF8String]);

その後、クラッシュします。

もう少し情報を編集します:

プールを排水した後:

NSLog(@"static: %@", staticString);  //this logs "static: static: "
NSLog(@"static: %@", [staticString description]); //this crashes

したがって、明らかに文字列に対してメソッドを呼び出すだけで、クラッシュするのに十分です。その場合、文字列を直接ログに記録してもクラッシュしないのはなぜですか? NSLog()メソッドを呼び出すべきではありません-descriptionか?

2 番目の「 static: 」はどこから来ているのですか? なぜこれがクラッシュしないのですか?


結果:

ケビン・バラードとグラハム・リーの両方が正しい. (私が誤って想定していたように) それが呼び出されてNSLog()ないことを認識した Graham の意見は正しかったし、Kevin は、これがフォーマット文字列のコピーと周り-descriptionの奇妙なスタック関連の問題であることはほぼ間違いなく正しい。va_list

  1. NSLoggingNSString呼び出しません-description。Graham はこれをエレガントに示しました。ロギングを行う Core Foundation のソースをたどると、これが事実であることがわかります。内部で発生したバックトレースは、 => => =>NSLogを呼び出していることを示しています。 (5365 行目) ですべての魔法が実行されます。すべての置換を手動で検索していることがわかります。置換の型が aであり、記述関数が非 nil であり、置換が別の型によってまだ処理されていない場合にのみ、記述コピー関数を呼び出すことになります。説明がコピーされていないことを示したので、NSLogv_CFLogvEx_CFStringCreateWithFormatAndArgumentsAux_CFStringAppendFormatAndArgumentsAux_CFStringAppendFormatAndArgumentsAux()%CFFormatObjectTypeNSStringより早く処理されます (その場合、おそらく未加工のバイト コピーを実行することになるでしょう)。
  2. Kevin が推測しているように、ここでスタック エラーが発生しています。どういうわけか、自動解放された文字列を指していたポインターがのオブジェクトに置き換えられていますNSString。だから、それはクラッシュしません。変。ただし、静的変数の型を などの別のものに変更するとNSArray-descriptionメソッドが呼び出され、プログラムは予想どおりにクラッシュします。

本当にまったく奇妙です。ポイントは、行動の根本原因について最も正確であるケビンに与えられ、グラハムに私の誤った考えを正したことを称賛します. 2つの答えを受け入れることができればいいのに...

4

5 に答える 5

9

あなたが見ていることに対する私の最良の推測は、 NSLog() がフォーマット文字列を (おそらく変更可能なコピーとして) コピーしてから、引数を解析することです。dealloc'dstaticStringしたので、たまたまフォーマット文字列のコピーが同じ場所に配置されています。これにより、"static: static: "説明した出力が表示されます。もちろん、この動作は定義されていません。常に同じメモリ位置を使用するという保証はありません。

一方、フォーマット文字列のコピーが発生する前にNSLog(@"static: %s", [staticString UTF8String])アクセスしているstaticStringため、ガベージ メモリにアクセスしています。

于 2011-01-18T23:01:05.420 に答える
8

NSLog()インスタンスを呼び出す-descriptionというあなたの仮定にNSStringは誤りがあります。このカテゴリを追加しました:

@implementation NSString (GLDescription)

- (NSString *)description {
  NSLog(@"-description called on %@", self);
  return self;
}

@end

再帰的に呼び出されないため、スタック オーバーフローは発生しません。それだけでなく、そのカテゴリを質問のコードに挿入すると、次の出力が見つかります。

2011-01-18 23:04:11.653 LogString[3769:a0f] -description called on 1
2011-01-18 23:04:11.656 LogString[3769:a0f] -description called on 2
2011-01-18 23:04:11.657 LogString[3769:a0f] -description called on 3
2011-01-18 23:04:11.658 LogString[3769:a0f] static: static: 

したがって、それはその引数に出くわすものをNSLog()呼び出さないと結論付けます。静的文字列を 2 回取得する理由は、解放された変数に誤ってアクセスしたときのスタック上のデータの異常である可能性があります。-descriptionNSStringstaticString

于 2011-01-18T23:07:43.413 に答える
1

割り当て解除されたメモリにアクセスしても、必ずしもクラッシュが発生するわけではありません。動作は未定義です。期待しすぎです!

于 2011-01-18T22:48:11.347 に答える
1

これは「使用後free()」の場合です。何が起こるかは「未定義動作」です。あなたの例は実際には次のようなものと同じです。

char *stringPtr = NULL;
stringPtr = malloc(1024); // Example code, assumes this returns non-NULL.
strcpy(stringPtr, "Zippers!");
free(stringPtr);
printf("Pants: %s\n", stringPtr);

ラインで何が起こりprintfますか?知るか。からPants: Zippers!までPants: (...garbage...) Core Dump

Objective-C固有のものはすべて実際には無関係です。重要なのは、もはや有効ではないメモリへのポインタを使用しているという事実だけです。クラッシュして印刷されない「理由」を説明しようとするよりも、壁にダーツを投げるほうがよいでしょうstatic: static。パフォーマンス上の理由から、ほとんどの実装では、必要になるまで割り当てをmalloc「取得」する必要はありません。free()私見、これがおそらくあなたの例があなたが期待したようにクラッシュしていない理由です。

この特定のプログラムのクラッシュを本当に確認したい場合は、次のいずれかを実行できます。

  • 環境変数CFZombieLevel17(落書き+解放しない)に設定します。
  • 環境変数NSZombieEnabledをに設定しますYES
  • 環境変数DYLD_INSERT_LIBRARIESをに設定します/usr/lib/libgmalloc.dylib(を参照man libgmalloc)。
于 2011-01-20T01:13:57.127 に答える
1

@"static:" が staticString と同じメモリ位置に格納されていることに関係があるのか​​もしれません。staticString の割り当てが解除され、@"static: %@" がリサイクルされたメモリの場所に格納されるため、staticString ポインターは "static: %@" にあるため、static: static: になります。

于 2011-01-18T22:53:53.437 に答える