4

ブロックのコピーに関するいくつかのトピックを検索しましたが、興味のある情報が見つかりませんでした。

ブロックを定義するとき、それを囲むスコープから変数をキャプチャする機会があります。ブロックはスタックに格納され、変数は値によってキャプチャされるため、ここではすべてが明確です。

  • プリミティブ型の場合、元のint変数と同じ値を持つ追加の変数(スタックにもローカライズされている)、たとえばconstintを取得します。
  • ポインタの場合、特定のポインタのコピーを取得します。これにより、ポイントされたオブジェクトの参照カウントが1つ増えます。

さて、スタックからヒープにブロックを移動(コピー)するとどうなるかわかりません。キャプチャされたポインタの場合、それは簡単です-それらのポインタのコピーを取得します。しかし、プリミティブ型のキャプチャされた変数はどうなりますか?ヒープ上の変数は動的に割り当てられるため、ポインターでのみ参照できます。これは、たとえばint変数をヒープに単純にコピーすることはできないことを意味します-int変数を動的に割り当て、それをint *ポインターに割り当て、そのポインターを介して適切な値を書き込むことができます-元のintと同じです変数。しかし、これには、舞台裏で機能する追加のメカニズムが必要になります。さらに、ブロック内の変数をキャプチャすると、ブロックは特定のサイズの変数を特定の方法で操作するように「準備」します。プリミティブ型の変数をポインターに変更すると、通常はサイズが異なり、それを処理する別の方法が必要になります...

それで、誰かが私に教えてもらえますか、それはどのように深く機能しますか?それとも、ある時点で私は単に間違っているのでしょうか?

4

4 に答える 4

7

厄介な詳細は、ブロック実装仕様にあります。

例を挙げて説明するのが最も簡単です。単純なブロックを含むこの単純な関数について考えてみます。

void outerFunction() {
    int x = 7;
    dispatch_block_t block = ^{
        printf("%d\n", x);
    };
    dispatch_sync(dispatch_get_main_queue(), block);
}

dispatch_block_tこれはのtypedefであることを忘れないでくださいvoid (^)(void)

そのコードをコンパイルするために、コンパイラーは最初に2つの構造体定義を作成します。

struct Block_descriptor_1 {
    unsigned long reserved;
    unsigned long size;
    const char *signature;
};

struct Block_literal_1 {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *);
    struct Block_descriptor_1 *descriptor;
    int x;
};

次に、タイプのグローバル変数を作成します。これには、ブロックのタイプシグネチャのBlock_descriptor_1サイズとエンコーディングが含まれます。Block_literal_1

struct Block_descriptor_1 block_descriptor_1 = {
    .size = sizeof(struct Block_literal_1),
    .signature = "v4@?0"
};

そして、ブロックの本体を含む関数を作成します。

void outerFunction_block_invoke(void *voidLiteral) {
    struct Block_literal_1 *literal = (struct Block_literal_1 *)voidLiteral;
    printf("%d\n", literal->x);
}

ブロックの本体が書き直さxれ、囲んでいるスコープからの変数へのアクセスがブロックリテラルのメンバーへのアクセスになることに注意してください。

最後に、コンパイラは元の関数を書き直して、スタック上にブロックリテラルを作成し、ブロックの代わりにそのブロックリテラルのアドレスを使用します。

void outerFunction2() {
    int x = 7;
    struct Block_literal_1 block_literal_1 = {
        .isa = __NSConcreteStackBlock,
        .flags  = BLOCK_HAS_SIGNATURE,
        .invoke = outerFunction_block_invoke,
        .descriptor = &block_descriptor_1,
        .x = x
    };
    dispatch_sync(dispatch_get_main_queue(),
        (__bridge dispatch_block_t)&block_literal_1);
}

ブロックリテラルは、特別なObjective-Cへのポインタで始まることに注意してくださいClass。これにより、ブロックリテラルをObjective-Cオブジェクトとして扱うことができます。

__block属性をローカル変数に追加するとx、より複雑になります。その場合、コンパイラは、変数に関する情報(変数がオブジェクトへのポインタである場合に変数を保持および解放する方法など)とともにその変数を保持するための別の構造を作成する必要があります。これはすべて、この回答の上部にリンクした仕様で説明されています。

于 2013-03-25T22:00:20.923 に答える
3

あなたが何を求めているのかはよくわかりませんが、ブロックが実際にどのように機能するかを簡単に説明すると、物事が明確になります。

ブロックは事実上、匿名クラスのインスタンスです。クラスには、キャプチャされたすべての変数のivarがあります。ブロックがインスタンス化されると、キャプチャされたすべての変数がブロック上のそれぞれのivarにコピーされ、その時点でスタックに格納されます。

ブロックがヒープにコピーされると、ヒープ上に新しいブロックオブジェクトが作成され、スタックブロックからヒープブロックにすべてのivarがコピーされます(この時点でキャプチャされたobj-cオブジェクトも保持されます)。プリミティブ値へのポインタなどについて混乱はありません。他のobj-cオブジェクトと同様に、キャプチャされたすべての値を含むヒープ上のmalloc領域は1つだけです。

一方、ブロック内の実際のコードはimplicit_block_pointer->backing_ivar、オブジェクトのメソッドがオブジェクトのivarにアクセスするのとまったく同じ方法で、と同等の機能を使用して、キャプチャされた変数にアクセスするだけです。

于 2013-03-25T21:33:04.227 に答える
2

Algol-68を使用してプログラミングすることを学ぶだけなら、あなたがしていることは、あるレベルのポインターを欠いています...(ref loc誰か?)

[以下は、何が起こっているかの要点を示すためにいくらか簡略化されています。]

変数を宣言するときは、次のように言います。

int x;

intの表現を格納できる場所を見つけ、その場所を名前を使用して参照する値に使用するようにコンパイラーに指示していますx

「検索」ビットをスキップして、コンパイラは内部テーブル、ここでは名前を場所にマップするシンボルテーブルxを作成します。場所は絶対アドレス(別名ポインタ)または何かからのオフセットとして表されます。たとえば、「スタックの7番目の場所」。何かがコンピュータ内のレジスタと呼ばれる特別な名前の場所に格納されることがあります。たとえば、スタックポインタが格納されるレジスタがあるため、スタックポインタへのオフセットとして格納される変数は、このレジスタに格納される値からのオフセットとして配置されます。

このテーブルを使用して、コンパイラは、の値の表現が格納されているx場所のアドレスを判別できます。x

マシン命令レベルでは、変数の読み取りまたは書き込みには、アドレスを取得する命令の使用が含まれます。したがって、実際のマシンコードにたどり着くまでに、すべての変数はポインターを介して参照されます。

次に、ブロックケースに移ります。整数変数の例をキャプチャするとx、コンパイラはブロックを記述する構造内でその場所を割り当てます。シンボルテーブルでは、エントリを作成し、 「ブロック変数領域xの6番目の場所」のようにマップします。ブロック変数領域の場所は、特定の場所、おそらく上記のスタックポインターと同じようにレジスターに配置され、マシン命令はその場所からのオフセットとしての値を見つけます。x

ブロックがスタックベースの場合、ブロック変数領域はスタック上にあり、ヒープベースの場合はヒープ内にありますが、ブロックが実行される前にその場所がレジスタに格納されるため、ブロックのコードは決してありません。変更する必要がxあります-ブロック変数領域に対して常に同じオフセットにあります。

うまくいけば、それはすべて理にかなっています!

于 2013-03-25T21:55:05.207 に答える
1

「舞台裏で機能する」追加のメカニズムは、C構造体のメンバーアクセスです。ヒープのスタック上にあるかどうかに関係なく、ブロックは、キャプチャされた各変数のメンバーを持つ構造体です(実際にはObjective-Cオブジェクトでもあります)。ブロックが実行されると、ブロックへのポインタを引数として取る関数が呼び出されます。この関数は、のようなキャプチャされた変数にアクセスしますblockPointer->capturedVar1blockPointerこの時点でポイントがどこを指しているかは関係ありません。重要なのは、構造体内のどこかにキャプチャされた変数に割り当てられたスペースがあることです。

あなたはこれが啓発的であると思うかもしれません:http://clang.llvm.org/docs/Block-ABI-Apple.html

于 2013-03-25T21:59:56.360 に答える