http://www.jianshu.com/p/51d04b7639f1
行指派是沒有意義的,是以編譯器給出了錯誤。我們可以通過位址傳遞來消除以上錯誤:
<code class="cpp">- (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)test
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> a = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>;
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 利用指針p存儲a的位址</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> *p = &a;
^{
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通過a的位址設定a的值</span>
*p = <span class="hljs-number" style="color: rgb(0, 102, 102);">10</span>;
};
}</code>
但是變量a的生命周期是和方法test的棧相關聯的,當test運作結束,棧随之銷毀,那麼變量a就會被銷毀,p也就成為了野指針。如果block是作為參數或者傳回值,這些類型都是跨棧的,也就是說再次調用會造成野指針錯誤。
OC Block 源碼
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
//printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
//printf("src dump: %s\n", _Block_byref_dump(src));
if (src->forwarding->flags & BLOCK_IS_GC) {
; // don't need to do any more work
}
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
//printf("making copy\n");
// src points to stack
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning
}
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// assign byref data block pointer into new Block
_Block_assign(src->forwarding, (void **)destp);
}
<code class="cpp"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_func_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// bound by ref</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 注意,這裡的_forwarding用來保證操作的始終是堆中的拷貝a,而不是棧中的a</span>
(a->__forwarding->a) = <span class="hljs-number" style="color: rgb(0, 102, 102);">10</span>;
}
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_copy_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*dst, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*src) {_Block_object_assign((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)&dst->a, (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)src->a, <span class="hljs-number" style="color: rgb(0, 102, 102);">8</span><span class="hljs-comment" style="color: rgb(136, 0, 0);">/*BLOCK_FIELD_IS_BYREF*/</span>);}
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_dispose_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*src) {_Block_object_dispose((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)src->a, <span class="hljs-number" style="color: rgb(0, 102, 102);">8</span><span class="hljs-comment" style="color: rgb(136, 0, 0);">/*BLOCK_FIELD_IS_BYREF*/</span>);}
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_desc_0 {
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">size_t</span> reserved;
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">size_t</span> Block_size;
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*copy)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*);
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*dispose)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">sizeof</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> _I_Person_test(Person * self, SEL _cmd) {
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// __block将a包裝成了一個對象</span>
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)<span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>,(__Block_byref_a_0 *)&a, <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">sizeof</span>(__Block_byref_a_0)};
;
(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*)())&__Person__test_block_impl_0((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, <span class="hljs-number" style="color: rgb(0, 102, 102);">570425344</span>);
}</code>
可以看到,對比上面的結果,明顯多了
__Block_byref_a_0
結構體,這個結構體中含有
isa
指針,是以也是一個對象,它是用來包裝局部變量a的。當block被copy到堆中時,
__Person__test_block_impl_0
的拷貝輔助函數
__Person__test_block_copy_0
會将
__Block_byref_a_0
拷貝至堆中,是以即使局部變量所在堆被銷毀,block依然能對堆中的局部變量進行操作。其中
__Block_byref_a_0
成員指針
__forwarding
用來指向它在堆中的拷貝,其依據源碼如下:
<code class="objectivec"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> _Block_byref_assign_copy(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *dest, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *arg, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> flags) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref **destp = (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref **)dest;
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref *src = (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref *)arg;
...
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 堆中拷貝的forwarding指向它自己</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>->forwarding = <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// patch heap copy to point to itself (skip write-barrier)</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0);">// 棧中的forwarding指向堆中的拷貝</span>
src->forwarding = <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// patch stack to point to heap copy</span>
...
}</code>
<code class="objectivec"></code><p class="p1" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;">當block從棧上被copy到堆上時,會調用__main_block_copy_0将__block類型的成員變量i從棧上複制到堆上;而當block被釋放時,相應地會調用__main_block_dispose_0來釋放__block類型的成員變量i。</span></p><p class="p2" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;">一會在棧上,一會在堆上,那如果棧上和堆上同時對該變量進行操作,怎麼辦?</span></p><p class="p3" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;"><span class="s5">這時候,__forwarding的作用就展現出來了:</span>當一個__block變量從棧上被複制到堆上時,棧上的那個__Block_byref_i_0結構體中的__forwarding指針也會指向堆上的結構<span class="s5">。</span></span></p>
這樣做是為了保證操作的值始終是堆中的拷貝,而不是棧中的值。(處理在局部變量所在棧還沒銷毀,就調用block來改變局部變量值的情況,如果沒有__forwarding指針,則修改無效)
Ian_He: @tripleCC 也就是說, 編譯器把用__block修飾的基本類型變量全部放到堆上包裝成對象了, "棧裡面的那個變量"在block内外都不會用到, 或者說是不存在.
以前覺得加了__block修飾後, 就能通路到外面了, 其實是加了__block之後, 外面全部都通路堆了.
學到很多, 感謝樓主的無私奉獻(´ε`)
回複 2015.07.23 15:23
口可口可口達: @Ian_He 感覺這麼說不嚴謹,應該是在block被copy(到堆上)後,__block修飾的變量才會被copy到堆上(__forwarding指向了堆上的__block修飾的變量)。