2011-01-26

『iOS 4プログラミングブック』 第5章マルチスレッド 補遺 その2

『iOS 4プログラミングブック』 第5章マルチスレッド 補遺 その2

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構造体を触っても平気です。

もはや誰得の情報なのか混迷を極めて来ましたが、つづきます。