3

clangアナライザーは、スタックベースのメモリが返されるかどうかを確認できます。

dispatch_block_t getPrintBlock (const char *msg) {
    return ^{
        printf("%s", msg);
    };
}

このエラーが発生します:returning block that lives on the local stack

このエラーはどういう意味ですか?

4

2 に答える 2

8

エラーは、メソッドが戻った後に無効になる値を返していることを意味します。これはブロックの問題だけではありません。次のことを考慮してください。

- (int *) badMethod
{
   int aLocalIntVariable;

   return &aLocalIntVariable; // return a reference to aLocalIntVariable, but that variable is about to die...
}

メソッドが入力されるとローカル変数が作成され、それらが存在する場所は「スタック」と呼ばれます。メソッドが戻ると、それらのローカル変数は破棄されます。このような変数に値を返すことはできますが、変数自体への参照を返すことはできません。無効になります。ローカル変数への参照を呼び出すメソッドに渡すことができます。その場合、ローカル変数はまだ存在します。

あなたの場合、あなたはブロックを作成しました。Objective-Cはたまたまスタック上に、つまり事実上匿名のローカル変数にブロック値を作成し、参照を使用してそれらを参照します。呼び出すメソッドへのそのような参照を渡すことはできますが、それを返すことはできません。その匿名ローカル変数は、他の変数と同じように破棄されます。

ただし、Objective-Cには、ブロック値のコピーをオブジェクトとして作成する2つの方法があります。これは「ヒープ」上に存在し、作成者よりも長持ちします。まずBlock_copy、関数があります:

<reference to heap allocated block> = Block_copy(<reference to stack allocated block>);

これはこれを行うための独自の方法であり、すべてでサポートされています-純粋なCコードを含め、ブロックはObjective-CだけでなくCの一部です。2番目の方法は、ブロックがすでにオブジェクトであると偽っcopyて、標準メッセージを送信できるようにします。

<reference to heap allocated block> = [<reference to stack allocated block> copy];

あなたが主にObjective-Cの人である場合、この2番目の方法はおそらくより快適に感じますが、そもそもなぜそれが必要なのかという問題を曖昧にします。

ARCはここで役立ちます。メモリ管理を自動化すると、ブロックがスタックからヒープに自動的にコピーされ(少なくとも現在のコンパイラでは、以前のコンパイラでは正しく機能しない可能性があります)、プログラマは実際の実装の詳細を無視できます。

補遺:ARC

上記の最後の段落は@newacctによって照会され、長いQ&Aコメント交換が行われました。その中の情報をわかりやすくするために、コメントを削除し、補足としてここに情報を統合しました。

ARCがブロックを処理する方法を理解するには、次の2つのドキュメントが役立ちます。

  1. Objective-Cの自動参照カウント、特にセクション3(ブロックは保持可能なオブジェクトポインタ)、3.2.3(保持可能なオブジェクトタイプはリターン境界を越えて有効)、および7.5(ブロックがコピーされるときのルール)。
  2. ARCリリースノートへの移行、特にFAQ項目「ARCでブロックはどのように機能しますか?」

これらから、ほとんどの場合、 ARCは、すべてのオブジェクトタイプの管理の一部として、スタックからヒープへのブロックのすべてのコピーを処理すると判断できます。

2番目の参照は、少なくともドキュメントが作成された時点では自動的に処理されなかった1つのケースを強調しています。その場合は、スタックに割り当てられたブロックが次のidようなタイプのメソッドパラメータに渡されます。

- (void) takeBlockWithTypeLoss:(id)block { ... }

[obj takeBlockWithTypeLoss:^{ ... }];

このような場合、ドキュメントの作成時に、ARCはブロックをコピーしませんでした。呼び出されたメソッドが渡されたブロックを保持する操作を実行すると、保持値がヒープ上にないため、問題が発生します。(問題が発生するには、ブロックをスタックで割り当てる必要があることに注意してください。環境内の変数を参照しないリテラルブロックは静的に割り当てられます。また、リテラルブロックは、デフォルトの強力な所有権を持つローカル変数に最初に格納され、次にに渡されます。メソッドがコピーされます。

このケースはタイプ損失の例であり、ブロックタイプとして知られている値が損失idタイプ情報として渡されます。コンパイラーは常にそのようなポイントを判別できるのに、なぜARCはブロックをコピーしないのですか(またはしましたか?)?過去に与えられた答えは単に効率の1つであり、コピーは必要ないかもしれず、多くの不要なコピーはパフォーマンスに打撃を与えます。

ただし、現在のコンパイラ(Xcode 4.6.1)は、この残りの1つのケースを処理しているように見えます。型が失われた時点で、ブロックがヒープにコピーされます。誰かがこれが文書化されていることを示すことができる場合(またはコンパイラがチェックをコーディングするなどしてこのケースを処理すると確信している場合)、それは履歴に追いやられる可能性がありますBlock_copy()(または[block copy])そうでない場合は、型の喪失が発生したときに使用する必要があります。

補遺:2013年6月

この質問で明らかになったように、Xcode 4.6.3 /Clang4.2では処理できない場合があります。ブロックが可変引数の1つとして可変引数メソッドに渡される場合、コンパイラはスタックブロックをヒープに自動的にプロモートしません。これは、上記のタイプ損失のサブケースです。

したがって、現在のコンパイラが処理しない場合があります。これは、仕様以上をサポートするコンパイラが文書化されていない理由を示唆しています。つまり、サポートが不完全です(ただし、これらは必要な理論上の理由ではありません)。

したがって、以前と同様に、型の喪失がある場合、コンパイラはブロックの昇格を自動的に処理しない場合があります(ただし、必要に応じてこれをテストできます)。型の喪失を伴わないケースは、仕様に従って自動的に処理されます。

(ところで、上記の質問へのコメントで言及された古い質問は、現在、仕様の対象であり、コンパイラーによって正しく処理されるケースの1つです。)

于 2013-03-14T20:38:51.927 に答える
7

ブロックをヒープに移動するには、ブロックのコピーを作成する必要があります。

つまり、次のようなものです。

dispatch_block_t createPrintBlock (const char *msg) {
    return Block_copy(^{
        printf("%s", msg);
    }) ;
}
于 2013-03-14T20:04:42.653 に答える