2011年1月27日発売でも、すでに書店に並びつつある『iOS 4プログラミングブック』。
今回も引き続き第5章マルチスレッドの補遺として、「__block修飾子」を深追いしてみます。
その1で書いたとおり、__block変数の実態は「total.__forwarding->total」て感じです。Block生成直後はstackに居るので、この
__forwarding
ポインタは自分が含まれる構造体を示し、Block_copyでstackからheapに移動されると(189ページ参照)、__forwarding
ポインタが移動後のアドレスを示すわけですね。「total.__forwarding->total」は、stackにあるか、heapにあるか、気にせずにアクセスできるわけです。確かめてみましょう!
#import <Foundation/Foundation.h>
#import <stdio.h>
extern const char *_Block_byref_dump(void *);
void dump(int line, int *p)
{
p -= 4;
printf("\ndump line:%d\n", line);
puts(_Block_byref_dump(p));
}
int *test()
{
__block int total = 11;
dump(__LINE__, &total);
void (^block_on_stack)() = ^{
++total;
dump(__LINE__, &total);
};
block_on_stack();
printf("\n___ Block_copy ___\n");
void (^block_on_heap)() = Block_copy(block_on_stack);
dump(__LINE__, &total);
block_on_stack();
block_on_heap();
printf("\n___ Block_release ___\n");
Block_release(block_on_heap);
dump(__LINE__, &total);
block_on_stack();
return &total;
}
int main()
{
dump(__LINE__, test());
}
__block変数をダンプするための秘密の関数 _Block_byref_dump を使って、それぞれの状況で__block変数の状態を表示してみます。このソースコードに埋めてみましょう。
int *test()
{
__block int total = 11;
dump(__LINE__, &total);
dump line:17
byref data block 0xbffffa70 contents:
forwarding: 0xbffffa70 ← stackに生成されてる
flags: 0x0
size: 20
void (^block_on_stack)() = ^{
++total;
dump(__LINE__, &total);
};
block_on_stack();
dump line:23
byref data block 0xbffffa70 contents:
forwarding: 0xbffffa70 ← stack上のBlockからstack上の__block変数を使用
flags: 0x0
size: 20
printf("\n___ Block_copy ___\n");
void (^block_on_heap)() = Block_copy(block_on_stack);
dump(__LINE__, &total);
dump line:31
byref data block 0x100150 contents:
forwarding: 0x100150 ← heapにコピーされた!
flags: 0x1000002 ← 要Block_releaseフラグと、参照カウンタ(2)
size: 20
block_on_stack();
dump line:23
byref data block 0x100150 contents:
forwarding: 0x100150 ← stack上のBlockからheap上の__block変数を参照している
flags: 0x1000002
size: 20
block_on_heap();
dump line:23
byref data block 0x100150 contents:
forwarding: 0x100150 ← heap上のBlockからheap上の__block変数を使用している
flags: 0x1000002
size: 20
printf("\n___ Block_release ___\n");
Block_release(block_on_heap);
dump(__LINE__, &total);
dump line:40
byref data block 0x100150 contents:
forwarding: 0x100150
flags: 0x1000001 ← Block_releaseしたので参照カウンタが減少(2→1)
size: 20
block_on_stack();
dump line:23
byref data block 0x100150 contents:
forwarding: 0x100150 ← stack上のBlockからheap上の__block変数を使用。まだ参照カウンタが1なのでheap上に残存
flags: 0x1000001
size: 20
return &total;
}
int main()
{
dump(__LINE__, test());
dump line:49
byref data block 0x100150 contents:
forwarding: 0x100150
flags: 0x1000000 ← 参照カウンタが0なのでheap上に残ってないはず!
size: 20
}
(Mac OS X 10.6、clang -m32で確認)
てことで、Block_copy使うと、本当にstackからheapに移動してることがわかりました。
ついでに、Block_releaseしても、heapからstackに戻すわけじゃなさそうなこともわかりました。
あ、dump関数が、「total.__forwarding->total」からのアクセスなので、Block_copy以降全部heapに存在しているように見えますが、stack上のtotal.__forwardingがちゃんとheap上のものをさしているので、stack上のtotal構造体を触っても平気です。
もはや誰得の情報なのか混迷を極めて来ましたが、つづきます。