Objective-C の __block の参照カウンタを調査中…
『エキスパートObjective-Cプログラミング』をBlocksまで読破。ブロックをcopy後は、__block変数の__forwarding先を現在のスコープ(?)が所有しスコープから抜けた時に開放するようにしないと、変数の寿命よりcopy後ブロックが短い場合に困るのでは
2011-11-24 17:58:17 via web
ないかと、そのような記述を探してみたがよくわからなかった。-rewrite-objcはブロック部分だけどC++へ展開するけど、それ以外はobjcのママなのであんまり手がかりにならないし。アセンブリを見るしかない?
2011-11-24 18:00:24 via web
@ganaware copyしたblockの方がスタックよりも寿命が短いケースは実装上想定されてないのではないかと考えています。このへんに動作をまとめていますので参考になるかと思います。 URL URL
2011-11-24 21:49:58 via web to @ganaware
という設定で上記の @splhack さんの記事のコード(以下に再掲)をビルドし、
#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()); }
Assembly を見ると、test() の最後で以下のようなコードが実行されているのが見つかります。
.loc 1 45 1 ## (略)/main.m:45:1 movl -112(%ebp), %eax ## 4-byte Reload movl %eax, (%esp) movl $8, 4(%esp) calll __Block_object_dispose
どうやら __block 変数の __forwarding 先の参照カウントは、Block_copy() した時にスタックからの参照分とヒープから参照分だけカウンタを増やしておいて、Block_release() 時とスコープの終わりに挿入される _Block_object_dispose() 時に減らしているようですね。
https://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/runtime.c
void _Block_object_dispose(const void *object, const int flags) { // (略) if (flags & BLOCK_FIELD_IS_BYREF) { // get rid of the __block data structure held in a Block _Block_byref_release(object); } // (略) }
というわけで
『エキスパートObjective-Cプログラミング』をBlocksまで読破。ブロックをcopy後は、__block変数の__forwarding先を現在のスコープ(?)が所有しスコープから抜けた時に開放するようにしないと、変数の寿命よりcopy後ブロックが短い場合に困るのでは
2011-11-24 17:58:17 via web
ところで、実行してみたところ次のような出力を得ました。
dump line:17 byref data block 0xbffff9c0 contents: forwarding: 0xbffff9c0 flags: 0x0 size: 20 dump line:23 byref data block 0xbffff9c0 contents: forwarding: 0xbffff9c0 flags: 0x0 size: 20 ___ Block_copy ___ dump line:31 byref data block 0x13e450 contents: forwarding: 0x13e450 flags: 0x1000004 size: 20 dump line:23 byref data block 0x13e450 contents: forwarding: 0x13e450 flags: 0x1000004 size: 20 dump line:23 byref data block 0x13e450 contents: forwarding: 0x13e450 flags: 0x1000004 size: 20 ___ Block_release ___ dump line:40 byref data block 0x13e450 contents: forwarding: 0x13e450 flags: 0x1000002 size: 20 dump line:23 byref data block 0x13e450 contents: forwarding: 0x13e450 flags: 0x1000002 size: 20 dump line:49 byref data block 0x13e450 contents: forwarding: 0x13e450 flags: 0x1000001 size: 20
@splhack さんの記事の結果とは以下の点が若干異なっています。
- 謎1: flags の下位 16bit の参照カウントが、Block_copy() で 4 増え、Block_release() で 2 減る
- _Block_object_dispose() 内では flags を 1 しか減らしていないように見えるのですが…
- 謎2: 最後の dump line:49 では参照カウントが 1 のまま
- 参照カウントは OSAtomicCompareAndSwapInt() で減らされるので、最後はちゃんと 0 になるべきであるはず
@ganaware おそらく私が調べた時とランタイムが異なるからだと思います。現在はlibclosure URL が使われているのではないかと。
2011-11-25 04:01:40 via YoruFukurou to @ganaware
というお返事をいただいたので libclosure-53 を覗いてみると、
http://www.opensource.apple.com/source/libclosure/libclosure-53/Block_private.h
enum { BLOCK_DEALLOCATING = (0x0001), BLOCK_REFCOUNT_MASK = (0xfffe), BLOCK_NEEDS_FREE = (1 << 24), (略) };
となっており、参照カウントは1bit左へシフトされていました。上記の結果になるのも納得です。