__block NSObject *obj 和块运行时的混淆
confusion on __block NSObject *obj and block runtime
我使用clang -rewrite-objc Block.m
生成Block.m
的c++代码。
Block.m
中的代码在ARC下:
void func() {
__block NSObject *obj = [[NSObject alloc] init];
void (^blk)(void) = ^() {
obj = nil;
};
}
我相信当块被复制并移动到堆中时,堆中的块将保留obj
。但是在深入挖掘block runtime的源码后,却得到了相反的结果
生成的c++代码:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __func_block_impl_0 {
struct __block_impl impl;
struct __func_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__func_block_impl_0(void *fp, struct __func_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __func_block_func_0(struct __func_block_impl_0 *__cself) {
__Block_byref_obj_0 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj) = __null;
}
static void __func_block_copy_0(struct __func_block_impl_0*dst, struct __func_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __func_block_dispose_0(struct __func_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __func_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __func_block_impl_0*, struct __func_block_impl_0*);
void (*dispose)(struct __func_block_impl_0*);
} __func_block_desc_0_DATA = { 0, sizeof(struct __func_block_impl_0), __func_block_copy_0, __func_block_dispose_0};
void func() {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
void (*blk)(void) = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
}
注意:33554432
是 BLOCK_HAS_COPY_DISPOSE
,570425344
是 BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR
。
当块被复制时,调用__func_block_copy_0
来处理它捕获的变量,在这种情况下它制作(__Block_byref_obj_0)obj
的副本,将obj->forwarding更改为副本__Block_byref_obj_0
等等,所有这些操作都发生在 _Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
.
_Block_object_assign
的源代码:
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
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);
}
由于flag
是BLOCK_FIELD_IS_BYREF
,所以分支到_Block_byref_assign_copy
,这个函数malloc内存复制了__Block_byref_obj_0
并做了一些赋值,最后会调用指向__Block_byref_id_object_copy_131
的(*src->byref_keep)(copy, src)
,我们在这个函数中可以看到,只有一行代码:
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
,131是BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER
,"(char*)dst+40"是BLOCK_FIELD_IS_BYREF
的地址(copy),所以会调用_Block_assign((void *)object, destAddr);
而这个函数只是做一个赋值 *destAddr = object;
, 没有 retain!!!
我认为obj
应该保留,但源代码似乎没有保留它。我真的很困惑。
我得到了 Blocks Runtime here 的源代码,你可以使用 svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
.
获取它
更新 1:
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
是也不是。它不直接 "retain" 它。我来解释一下。
obj
是一个 __block
变量。因此,该块在某种程度上将 "reference" 保存到变量 obj
,而不是像其他变量那样保存它的副本。
你说块 "retain" 捕获了对象指针变量,那是因为块保留捕获的常规(非__block
)变量的内部副本,并保留强对象指针的新副本变量需要保留它。
但是,在这种情况下,因为它是__block
,所以变量只有一个副本。块(或可能是块)和变量的原始范围都共享对变量的相同副本的访问。该块没有变量的独立副本。那么为什么块会保留它指向的对象呢? (想象一下,如果块保留了它,并且一个块更改了变量指向的对象,那么所有其他块如何知道释放旧对象并保留新对象?)
(请注意,__block
变量从堆栈开始并移至堆,类似于块,但这种优化对于内存管理讨论并不重要,因为始终只有一个活动副本变量的。)
另一种思考方式是,__block
变量实际上表现得好像它们被包裹在某种透明的 "holder" 对象中以实现共享状态。 "holder" 对象将包装变量保存为内部字段,如果它是对象指针类型,它会保留包装变量。但是,使用此变量的块(或多个块)仅包含对 "holder" 对象的引用,而不是对包装变量本身的引用。对变量的所有访问都是通过对 "holder" 对象的引用间接进行的。因此,当块被复制时,它们会保留 "holder" 对象,而不是里面的变量。当所有使用这个 __block
变量的块都被释放时,那么就没有对 "holder" 对象的引用了,如果 "holder" 对象的析构函数将释放内部变量是对象指针类型。
所以参考图是这样的:
block --> holder object --> NSObject object
块间接保留对象,但不是直接。
更新: 所以你似乎想知道为什么,如果 "holder object" 将底层指针变量视为强引用,你看不到 "holder object"分别保留和释放__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
中的底层变量。
实际上,如果您编译代码,您会看到一些不同的东西。 运行 clang -S -emit-llvm -fobjc-arc Block.m
,您将获得文本 LLVM IR,其中包括 "holder object" 的以下复制和处置助手:
; Function Attrs: ssp uwtable
define internal void @__Block_byref_object_copy_(i8*, i8*) #0 {
%3 = alloca i8*, align 8
%4 = alloca i8*, align 8
store i8* %0, i8** %3, align 8
store i8* %1, i8** %4, align 8
%5 = load i8*, i8** %3, align 8
%6 = bitcast i8* %5 to %struct.__block_byref_obj*
%7 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 6
%8 = load i8*, i8** %4, align 8
%9 = bitcast i8* %8 to %struct.__block_byref_obj*
%10 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %9, i32 0, i32 6
%11 = load %0*, %0** %10, align 8
store %0* null, %0** %7, align 8
%12 = bitcast %0** %7 to i8**
%13 = bitcast %0* %11 to i8*
call void @objc_storeStrong(i8** %12, i8* %13) #2
%14 = bitcast %0** %10 to i8**
call void @objc_storeStrong(i8** %14, i8* null) #2
ret void
}
; Function Attrs: ssp uwtable
define internal void @__Block_byref_object_dispose_(i8*) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** %2, align 8
%4 = bitcast i8* %3 to %struct.__block_byref_obj*
%5 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %4, i32 0, i32 6
%6 = bitcast %0** %5 to i8**
call void @objc_storeStrong(i8** %6, i8* null) #2
ret void
}
在__Block_byref_object_copy_
函数中,执行了一个objc_storeStrong
,将holder对象的栈版变量的值强赋值给堆版变量(即带retain) holder 对象的,然后执行另一个 objc_storeStrong
将 nil
强分配给 holder 对象的堆栈版本中的变量(即释放之前的内容)。本质上,这就是以下代码在 ARC 中的作用:
heap_holder->var = stack_holder->var;
stack_holder->var = nil;
在 __Block_byref_object_dispose_
函数中,它执行 objc_storeStrong
将 nil
强赋值(即释放之前的内容)到 holder 对象的堆版本中的变量。本质上,这就是以下代码在 ARC 中的作用:
heap_holder->var = nil;
这与您在 "rewriter" 生成的代码中得到的非常不同。我猜重写器可能没有考虑 ARC——换句话说,它执行 MRC -> MRC 重写。如果这是 MRC -> MRC 重写,生成的代码确实是正确的,因为 __block
变量永远不会保留在 MRC 中,即使它们是对象指针类型。 (还有其他证据表明这不是正确的 ARC -> ARC 重写。例如,对 alloc
和 init
的调用被简单地重写为对 objc_msgSend
的调用。但是 alloc
是 ARC 中的 ns_returns_retained
方法,而 objc_msgSend
不是 ns_returns_retained
,因此将前者转换为后者会导致它们无法补救的保留不匹配。但如果这是MRC -> MRC 重写,这很好,因为用户负责显式调用 retain/release。)
的确,如果你在没有 ARC 的情况下使用 clang -S -emit-llvm Block.m
再次编译它,你会看到 __Block_byref_object_copy_
和 __Block_byref_object_dispose_
函数确实使用了 _Block_object_assign
和 _Block_object_dispose
,分别匹配您重写的代码显示的内容。
如果我们查看块代码生成的 Clang 源代码,在构建 byref 助手的部分,CodeGenFunction::buildByrefHelpers
, there is an if in there (line 1970) where it checks if the variable has a "lifetime" (which I think means it's a managed type under ARC), and if so, it builds using ARCWeakByrefHelpers
or ARCStrongByrefHelpers
or ARCStrongBlockByrefHelpers
; but if it doesn't have a lifetime, it builds using ObjectByrefHelpers
. Looking at ARCStrongByrefHelpers
for example, we see that it emits two store-strongs in the copy helper, and a destroy-strong in the dispose helper, which is what we see in the ARC compiled code. On the other hand, in ObjectByrefHelpers
,我们看到它在复制助手和块释放中发出块对象分配在 dispose helper 中,这是我们在 MRC 编译代码中看到的。
但是如果你查看 Objective-C 重写器的源代码,生成 byref 复制和处理助手的方法,RewriteModernObjC::SynthesizeByrefCopyDestroyHelper
, always generates _Block_object_assign
and _Block_object_dispose
. So this matches the hypothesis that the rewriter only does an MRC rewrite, though I cannot find any documentation on the rewriter so I cannot say whether this is by design or not. There is an answer 另一个表明 ARC 在代码生成器上运行的问题级别,这可能是源到源重写器不考虑它的原因(?)。
我认为您在分析中遗漏的是您没有看到为 ARC 插入的调用,而是看到了语言级别的分配。
在 __Block_byref_obj_0
中,您将字段 obj
显示为:
NSObject *obj;
没有任何明确的 strong 所有权限定符。当我 运行 类似的测试时 Clang 输出:
NSObject *__strong obj;
包括明确的所有权限定符。
如果您查看汇编级代码,您会发现在不同位置插入了对 ARC 内存管理例程的调用。
因此,您所看到的简单赋值实际上可能会编译成强大的存储 - 即释放任何现有引用,保留新引用。这当然和原来的完全一样Objective-C,你"read in"将ARC语义作为语言语义的一个组成部分。
HTH
多亏了@CRD和@newacct的指点,我分了汇编代码,找到了一些线索。我将post这里的汇编代码做一些分析
汇编代码目标是armv7,所以long和pointer占用4个字节供您参考。
我的第一个目标是找到我的问题中引用的 __Block_byref_id_object_copy
函数,该函数用于处理 NSObject *obj
将块复制到堆的位置,它位于 __Block_byref_obj_0
,让我们回顾一下结构。
让我们使用 "holder object" 而不是 __Block_byref_obj_0
来简化。
struct __Block_byref_obj_0 {
void *__isa; // address 0
__Block_byref_obj_0 *__forwarding; // address 4
int __flags; // address 8
int __size; // address 12
void (*__Block_byref_id_object_copy)(void*, void*); // address 16
void (*__Block_byref_id_object_dispose)(void*); // address 20
NSObject *obj; // address 24
};
如注释所示,__Block_byref_id_object_copy
位于 holder 对象的地址 + 16,obj
位于 holder 对象的地址 + 24。
现在我post部分主要功能在Block.m.
@ BB#0:
push {r4, r5, r6, r7, lr}
add r7, sp, #12
push.w {r8, r10, r11}
sub.w r4, sp, #64
bfc r4, #0, #4
mov sp, r4
vst1.64 {d8, d9, d10, d11}, [r4:128]!
vst1.64 {d12, d13, d14, d15}, [r4:128]
sub sp, #160
movs r0, #0
.loc 1 27 23 prologue_end
Ltmp3:
str r0, [sp, #80]
add r1, sp, #80
str r1, [sp, #84]
mov.w r2, #838860800
str r2, [sp, #88]
movs r2, #28
str r2, [sp, #92]
movw r2, :lower16:(___Block_byref_object_copy_-(LPC0_2+4))
movt r2, :upper16:(___Block_byref_object_copy_-(LPC0_2+4))
LPC0_2:
add r2, pc
str r2, [sp, #96]
movw r2, :lower16:(___Block_byref_object_dispose_-(LPC0_3+4))
movt r2, :upper16:(___Block_byref_object_dispose_-(LPC0_3+4))
LPC0_3:
add r2, pc
str r2, [sp, #100]
add.w r2, r1, #24
.loc 1 27 30 is_stmt 0
movw r3, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_4+4))
movt r3, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_4+4))
LPC0_4:
add r3, pc
ldr r3, [r3]
movw r9, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_5+4))
movt r9, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_5+4))
LPC0_5:
add r9, pc
ldr.w r9, [r9]
str r0, [sp, #40] @ 4-byte Spill
mov r0, r3
str r1, [sp, #36] @ 4-byte Spill
mov r1, r9
str r2, [sp, #32] @ 4-byte Spill
blx _objc_msgSend
.loc 1 27 29
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
LPC0_6:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
.loc 1 27 23
str r0, [sp, #104]
.loc 1 27 5
ldr r0, [sp, #32] @ 4-byte Reload
.loc 1 28 25 is_stmt 1
movw r1, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_7+4))
movt r1, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_7+4))
LPC0_7:
add r1, pc
ldr r1, [r1]
str r1, [sp, #52]
mov.w r1, #-1040187392
str r1, [sp, #56]
ldr r1, [sp, #40] @ 4-byte Reload
str r1, [sp, #60]
movw r2, :lower16:(___func_block_invoke-(LPC0_8+4))
movt r2, :upper16:(___func_block_invoke-(LPC0_8+4))
LPC0_8:
add r2, pc
str r2, [sp, #64]
movw r2, :lower16:(___block_descriptor_tmp-(LPC0_9+4))
movt r2, :upper16:(___block_descriptor_tmp-(LPC0_9+4))
看看这些代码:
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
LPC0_6:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
.loc 1 27 23
str r0, [sp, #104]
L_OBJC_SELECTOR_REFERENCES_.2
是init
方法,而return对象在r0中,r0则存储在sp+104
中,所以我知道__Block_byref_id_object_copy
一定存储在 sp+96
,它是 ___Block_byref_object_copy_
。
现在让我们关注___Block_byref_object_copy_
。
push {r7, lr}
mov r7, sp
sub sp, #12
movs r2, #0
str r0, [sp, #8]
str r1, [sp, #4]
ldr r0, [sp, #8]
mov r1, r0
adds r1, #24
ldr r3, [sp, #4]
mov r9, r3
add.w r9, r9, #24
ldr r3, [r3, #24]
str r2, [r0, #24]
mov r0, r1
mov r1, r3
str.w r9, [sp]
bl _objc_storeStrong
movs r1, #0
ldr r0, [sp]
bl _objc_storeStrong
add sp, #12
pop {r7, pc}
提醒一下,static void __Block_byref_id_object_copy_131(void *dst, void *src)
和___Block_byref_object_copy_
是一样的,我不知道编译器背后做了什么,但它确实改变了函数名,没关系,我需要知道它第一个参数是目标holder对象的地址,第二个参数是源holder对象的地址。
所以在___Block_byref_object_copy_
中,r0存放的是destination holder的地址,r1存放的是source holder的地址。
str r0, [sp, #8]
str r1, [sp, #4]
ldr r0, [sp, #8]
mov r1, r0
它存储r0到sp+8和r1到sp+4,将r0移动到r1。所以 r1=r0=dst 持有者的地址。
adds r1, #24
ldr r3, [sp, #4]
mov r9, r3
add.w r9, r9, #24
然后r1加上r1+24,所以r1存储了dst holder中obj的地址,将[sp+4]处的值加载到r3,所以r3存储了src holder的地址,将r3移动到r9,将r9+24加到r9上,所以r9中有obj在src holder中的地址。
ldr r3, [r3, #24]
str r2, [r0, #24]
mov r0, r1
mov r1, r3
str.w r9, [sp]
将[r3+24]中的值加载到r3中,所以r3将obj存储在src holder中,将r2存储到r0+24中,r2为零,因此dst holder中的obj为NULL。然后将r1移动到r0,r0有obj在dst holder中的地址,将r3移动到r1,所以r1将obj存储在src holder中。
bl _objc_storeStrong
movs r1, #0
ldr r0, [sp]
bl _objc_storeStrong
要了解什么是 objc_storeStrong
,请检查 here
调用bl _objc_storeStrong
时,r0为dst holder中obj的地址,r1为src holder中的obj。 objc_storeStrong
将保留src holder 中的obj,并将其分配给dst holder 中的obj。
然后r1赋0,r0加载[sp],[sp]存放obj在src holder中的地址
bl _objc_storeStrong
正在做 objc_storeStrong(&obj_src_holder, nil)
,所以 obj_src_holder 将被发送释放方法并分配给 nil。
结论:不知道为什么释放source holder中的obj并赋值给nil,不应该在func()
结束时释放吗?
然而,destination holder 中的 obj 确实对 NSObject *obj
保持强引用,所以我认为这应该是一个合理的答案。
我使用clang -rewrite-objc Block.m
生成Block.m
的c++代码。
Block.m
中的代码在ARC下:
void func() {
__block NSObject *obj = [[NSObject alloc] init];
void (^blk)(void) = ^() {
obj = nil;
};
}
我相信当块被复制并移动到堆中时,堆中的块将保留obj
。但是在深入挖掘block runtime的源码后,却得到了相反的结果
生成的c++代码:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __func_block_impl_0 {
struct __block_impl impl;
struct __func_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__func_block_impl_0(void *fp, struct __func_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __func_block_func_0(struct __func_block_impl_0 *__cself) {
__Block_byref_obj_0 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj) = __null;
}
static void __func_block_copy_0(struct __func_block_impl_0*dst, struct __func_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __func_block_dispose_0(struct __func_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __func_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __func_block_impl_0*, struct __func_block_impl_0*);
void (*dispose)(struct __func_block_impl_0*);
} __func_block_desc_0_DATA = { 0, sizeof(struct __func_block_impl_0), __func_block_copy_0, __func_block_dispose_0};
void func() {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
void (*blk)(void) = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
}
注意:33554432
是 BLOCK_HAS_COPY_DISPOSE
,570425344
是 BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR
。
当块被复制时,调用__func_block_copy_0
来处理它捕获的变量,在这种情况下它制作(__Block_byref_obj_0)obj
的副本,将obj->forwarding更改为副本__Block_byref_obj_0
等等,所有这些操作都发生在 _Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
.
_Block_object_assign
的源代码:
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
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);
}
由于flag
是BLOCK_FIELD_IS_BYREF
,所以分支到_Block_byref_assign_copy
,这个函数malloc内存复制了__Block_byref_obj_0
并做了一些赋值,最后会调用指向__Block_byref_id_object_copy_131
的(*src->byref_keep)(copy, src)
,我们在这个函数中可以看到,只有一行代码:
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
,131是BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER
,"(char*)dst+40"是BLOCK_FIELD_IS_BYREF
的地址(copy),所以会调用_Block_assign((void *)object, destAddr);
而这个函数只是做一个赋值 *destAddr = object;
, 没有 retain!!!
我认为obj
应该保留,但源代码似乎没有保留它。我真的很困惑。
我得到了 Blocks Runtime here 的源代码,你可以使用 svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
.
更新 1:
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
是也不是。它不直接 "retain" 它。我来解释一下。
obj
是一个 __block
变量。因此,该块在某种程度上将 "reference" 保存到变量 obj
,而不是像其他变量那样保存它的副本。
你说块 "retain" 捕获了对象指针变量,那是因为块保留捕获的常规(非__block
)变量的内部副本,并保留强对象指针的新副本变量需要保留它。
但是,在这种情况下,因为它是__block
,所以变量只有一个副本。块(或可能是块)和变量的原始范围都共享对变量的相同副本的访问。该块没有变量的独立副本。那么为什么块会保留它指向的对象呢? (想象一下,如果块保留了它,并且一个块更改了变量指向的对象,那么所有其他块如何知道释放旧对象并保留新对象?)
(请注意,__block
变量从堆栈开始并移至堆,类似于块,但这种优化对于内存管理讨论并不重要,因为始终只有一个活动副本变量的。)
另一种思考方式是,__block
变量实际上表现得好像它们被包裹在某种透明的 "holder" 对象中以实现共享状态。 "holder" 对象将包装变量保存为内部字段,如果它是对象指针类型,它会保留包装变量。但是,使用此变量的块(或多个块)仅包含对 "holder" 对象的引用,而不是对包装变量本身的引用。对变量的所有访问都是通过对 "holder" 对象的引用间接进行的。因此,当块被复制时,它们会保留 "holder" 对象,而不是里面的变量。当所有使用这个 __block
变量的块都被释放时,那么就没有对 "holder" 对象的引用了,如果 "holder" 对象的析构函数将释放内部变量是对象指针类型。
所以参考图是这样的:
block --> holder object --> NSObject object
块间接保留对象,但不是直接。
更新: 所以你似乎想知道为什么,如果 "holder object" 将底层指针变量视为强引用,你看不到 "holder object"分别保留和释放__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
中的底层变量。
实际上,如果您编译代码,您会看到一些不同的东西。 运行 clang -S -emit-llvm -fobjc-arc Block.m
,您将获得文本 LLVM IR,其中包括 "holder object" 的以下复制和处置助手:
; Function Attrs: ssp uwtable
define internal void @__Block_byref_object_copy_(i8*, i8*) #0 {
%3 = alloca i8*, align 8
%4 = alloca i8*, align 8
store i8* %0, i8** %3, align 8
store i8* %1, i8** %4, align 8
%5 = load i8*, i8** %3, align 8
%6 = bitcast i8* %5 to %struct.__block_byref_obj*
%7 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 6
%8 = load i8*, i8** %4, align 8
%9 = bitcast i8* %8 to %struct.__block_byref_obj*
%10 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %9, i32 0, i32 6
%11 = load %0*, %0** %10, align 8
store %0* null, %0** %7, align 8
%12 = bitcast %0** %7 to i8**
%13 = bitcast %0* %11 to i8*
call void @objc_storeStrong(i8** %12, i8* %13) #2
%14 = bitcast %0** %10 to i8**
call void @objc_storeStrong(i8** %14, i8* null) #2
ret void
}
; Function Attrs: ssp uwtable
define internal void @__Block_byref_object_dispose_(i8*) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** %2, align 8
%4 = bitcast i8* %3 to %struct.__block_byref_obj*
%5 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %4, i32 0, i32 6
%6 = bitcast %0** %5 to i8**
call void @objc_storeStrong(i8** %6, i8* null) #2
ret void
}
在__Block_byref_object_copy_
函数中,执行了一个objc_storeStrong
,将holder对象的栈版变量的值强赋值给堆版变量(即带retain) holder 对象的,然后执行另一个 objc_storeStrong
将 nil
强分配给 holder 对象的堆栈版本中的变量(即释放之前的内容)。本质上,这就是以下代码在 ARC 中的作用:
heap_holder->var = stack_holder->var;
stack_holder->var = nil;
在 __Block_byref_object_dispose_
函数中,它执行 objc_storeStrong
将 nil
强赋值(即释放之前的内容)到 holder 对象的堆版本中的变量。本质上,这就是以下代码在 ARC 中的作用:
heap_holder->var = nil;
这与您在 "rewriter" 生成的代码中得到的非常不同。我猜重写器可能没有考虑 ARC——换句话说,它执行 MRC -> MRC 重写。如果这是 MRC -> MRC 重写,生成的代码确实是正确的,因为 __block
变量永远不会保留在 MRC 中,即使它们是对象指针类型。 (还有其他证据表明这不是正确的 ARC -> ARC 重写。例如,对 alloc
和 init
的调用被简单地重写为对 objc_msgSend
的调用。但是 alloc
是 ARC 中的 ns_returns_retained
方法,而 objc_msgSend
不是 ns_returns_retained
,因此将前者转换为后者会导致它们无法补救的保留不匹配。但如果这是MRC -> MRC 重写,这很好,因为用户负责显式调用 retain/release。)
的确,如果你在没有 ARC 的情况下使用 clang -S -emit-llvm Block.m
再次编译它,你会看到 __Block_byref_object_copy_
和 __Block_byref_object_dispose_
函数确实使用了 _Block_object_assign
和 _Block_object_dispose
,分别匹配您重写的代码显示的内容。
如果我们查看块代码生成的 Clang 源代码,在构建 byref 助手的部分,CodeGenFunction::buildByrefHelpers
, there is an if in there (line 1970) where it checks if the variable has a "lifetime" (which I think means it's a managed type under ARC), and if so, it builds using ARCWeakByrefHelpers
or ARCStrongByrefHelpers
or ARCStrongBlockByrefHelpers
; but if it doesn't have a lifetime, it builds using ObjectByrefHelpers
. Looking at ARCStrongByrefHelpers
for example, we see that it emits two store-strongs in the copy helper, and a destroy-strong in the dispose helper, which is what we see in the ARC compiled code. On the other hand, in ObjectByrefHelpers
,我们看到它在复制助手和块释放中发出块对象分配在 dispose helper 中,这是我们在 MRC 编译代码中看到的。
但是如果你查看 Objective-C 重写器的源代码,生成 byref 复制和处理助手的方法,RewriteModernObjC::SynthesizeByrefCopyDestroyHelper
, always generates _Block_object_assign
and _Block_object_dispose
. So this matches the hypothesis that the rewriter only does an MRC rewrite, though I cannot find any documentation on the rewriter so I cannot say whether this is by design or not. There is an answer 另一个表明 ARC 在代码生成器上运行的问题级别,这可能是源到源重写器不考虑它的原因(?)。
我认为您在分析中遗漏的是您没有看到为 ARC 插入的调用,而是看到了语言级别的分配。
在 __Block_byref_obj_0
中,您将字段 obj
显示为:
NSObject *obj;
没有任何明确的 strong 所有权限定符。当我 运行 类似的测试时 Clang 输出:
NSObject *__strong obj;
包括明确的所有权限定符。
如果您查看汇编级代码,您会发现在不同位置插入了对 ARC 内存管理例程的调用。
因此,您所看到的简单赋值实际上可能会编译成强大的存储 - 即释放任何现有引用,保留新引用。这当然和原来的完全一样Objective-C,你"read in"将ARC语义作为语言语义的一个组成部分。
HTH
多亏了@CRD和@newacct的指点,我分了汇编代码,找到了一些线索。我将post这里的汇编代码做一些分析
汇编代码目标是armv7,所以long和pointer占用4个字节供您参考。
我的第一个目标是找到我的问题中引用的 __Block_byref_id_object_copy
函数,该函数用于处理 NSObject *obj
将块复制到堆的位置,它位于 __Block_byref_obj_0
,让我们回顾一下结构。
让我们使用 "holder object" 而不是 __Block_byref_obj_0
来简化。
struct __Block_byref_obj_0 {
void *__isa; // address 0
__Block_byref_obj_0 *__forwarding; // address 4
int __flags; // address 8
int __size; // address 12
void (*__Block_byref_id_object_copy)(void*, void*); // address 16
void (*__Block_byref_id_object_dispose)(void*); // address 20
NSObject *obj; // address 24
};
如注释所示,__Block_byref_id_object_copy
位于 holder 对象的地址 + 16,obj
位于 holder 对象的地址 + 24。
现在我post部分主要功能在Block.m.
@ BB#0:
push {r4, r5, r6, r7, lr}
add r7, sp, #12
push.w {r8, r10, r11}
sub.w r4, sp, #64
bfc r4, #0, #4
mov sp, r4
vst1.64 {d8, d9, d10, d11}, [r4:128]!
vst1.64 {d12, d13, d14, d15}, [r4:128]
sub sp, #160
movs r0, #0
.loc 1 27 23 prologue_end
Ltmp3:
str r0, [sp, #80]
add r1, sp, #80
str r1, [sp, #84]
mov.w r2, #838860800
str r2, [sp, #88]
movs r2, #28
str r2, [sp, #92]
movw r2, :lower16:(___Block_byref_object_copy_-(LPC0_2+4))
movt r2, :upper16:(___Block_byref_object_copy_-(LPC0_2+4))
LPC0_2:
add r2, pc
str r2, [sp, #96]
movw r2, :lower16:(___Block_byref_object_dispose_-(LPC0_3+4))
movt r2, :upper16:(___Block_byref_object_dispose_-(LPC0_3+4))
LPC0_3:
add r2, pc
str r2, [sp, #100]
add.w r2, r1, #24
.loc 1 27 30 is_stmt 0
movw r3, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_4+4))
movt r3, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_4+4))
LPC0_4:
add r3, pc
ldr r3, [r3]
movw r9, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_5+4))
movt r9, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_5+4))
LPC0_5:
add r9, pc
ldr.w r9, [r9]
str r0, [sp, #40] @ 4-byte Spill
mov r0, r3
str r1, [sp, #36] @ 4-byte Spill
mov r1, r9
str r2, [sp, #32] @ 4-byte Spill
blx _objc_msgSend
.loc 1 27 29
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
LPC0_6:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
.loc 1 27 23
str r0, [sp, #104]
.loc 1 27 5
ldr r0, [sp, #32] @ 4-byte Reload
.loc 1 28 25 is_stmt 1
movw r1, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_7+4))
movt r1, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_7+4))
LPC0_7:
add r1, pc
ldr r1, [r1]
str r1, [sp, #52]
mov.w r1, #-1040187392
str r1, [sp, #56]
ldr r1, [sp, #40] @ 4-byte Reload
str r1, [sp, #60]
movw r2, :lower16:(___func_block_invoke-(LPC0_8+4))
movt r2, :upper16:(___func_block_invoke-(LPC0_8+4))
LPC0_8:
add r2, pc
str r2, [sp, #64]
movw r2, :lower16:(___block_descriptor_tmp-(LPC0_9+4))
movt r2, :upper16:(___block_descriptor_tmp-(LPC0_9+4))
看看这些代码:
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4))
LPC0_6:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
.loc 1 27 23
str r0, [sp, #104]
L_OBJC_SELECTOR_REFERENCES_.2
是init
方法,而return对象在r0中,r0则存储在sp+104
中,所以我知道__Block_byref_id_object_copy
一定存储在 sp+96
,它是 ___Block_byref_object_copy_
。
现在让我们关注___Block_byref_object_copy_
。
push {r7, lr}
mov r7, sp
sub sp, #12
movs r2, #0
str r0, [sp, #8]
str r1, [sp, #4]
ldr r0, [sp, #8]
mov r1, r0
adds r1, #24
ldr r3, [sp, #4]
mov r9, r3
add.w r9, r9, #24
ldr r3, [r3, #24]
str r2, [r0, #24]
mov r0, r1
mov r1, r3
str.w r9, [sp]
bl _objc_storeStrong
movs r1, #0
ldr r0, [sp]
bl _objc_storeStrong
add sp, #12
pop {r7, pc}
提醒一下,static void __Block_byref_id_object_copy_131(void *dst, void *src)
和___Block_byref_object_copy_
是一样的,我不知道编译器背后做了什么,但它确实改变了函数名,没关系,我需要知道它第一个参数是目标holder对象的地址,第二个参数是源holder对象的地址。
所以在___Block_byref_object_copy_
中,r0存放的是destination holder的地址,r1存放的是source holder的地址。
str r0, [sp, #8]
str r1, [sp, #4]
ldr r0, [sp, #8]
mov r1, r0
它存储r0到sp+8和r1到sp+4,将r0移动到r1。所以 r1=r0=dst 持有者的地址。
adds r1, #24
ldr r3, [sp, #4]
mov r9, r3
add.w r9, r9, #24
然后r1加上r1+24,所以r1存储了dst holder中obj的地址,将[sp+4]处的值加载到r3,所以r3存储了src holder的地址,将r3移动到r9,将r9+24加到r9上,所以r9中有obj在src holder中的地址。
ldr r3, [r3, #24]
str r2, [r0, #24]
mov r0, r1
mov r1, r3
str.w r9, [sp]
将[r3+24]中的值加载到r3中,所以r3将obj存储在src holder中,将r2存储到r0+24中,r2为零,因此dst holder中的obj为NULL。然后将r1移动到r0,r0有obj在dst holder中的地址,将r3移动到r1,所以r1将obj存储在src holder中。
bl _objc_storeStrong
movs r1, #0
ldr r0, [sp]
bl _objc_storeStrong
要了解什么是 objc_storeStrong
,请检查 here
调用bl _objc_storeStrong
时,r0为dst holder中obj的地址,r1为src holder中的obj。 objc_storeStrong
将保留src holder 中的obj,并将其分配给dst holder 中的obj。
然后r1赋0,r0加载[sp],[sp]存放obj在src holder中的地址
bl _objc_storeStrong
正在做 objc_storeStrong(&obj_src_holder, nil)
,所以 obj_src_holder 将被发送释放方法并分配给 nil。
结论:不知道为什么释放source holder中的obj并赋值给nil,不应该在func()
结束时释放吗?
然而,destination holder 中的 obj 确实对 NSObject *obj
保持强引用,所以我认为这应该是一个合理的答案。