__block NSObject * obj和块运行时的混淆

时间:2016-05-02 23:48:09

标签: ios objective-c block

我使用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。但是在深入研究块运行时的源代码之后,我得到了相反的结果。

生成的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));
}

注意:33554432BLOCK_HAS_COPY_DISPOSE570425344BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR

复制块时,调用__func_block_copy_0来处理它捕获的变量,在这种情况下,它会复制(__Block_byref_obj_0)obj,更改obj->转发到副本__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 *)&copy->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);
}

由于flagBLOCK_FIELD_IS_BYREF,所以分支转到_Block_byref_assign_copy,此函数malloc内存用于__Block_byref_obj_0的副本并执行一些分配,最后它将调用{ {1}}指向(*src->byref_keep)(copy, src),正如我们在此函数中看到的那样,只有一行代码:

__Block_byref_id_object_copy_131,131是_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);,“(char *)dst + 40”是BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER(副本)的地址,因此会调用BLOCK_FIELD_IS_BYREF这个函数只做一个赋值_Block_assign((void *)object, destAddr);,没有保留!!!

我认为应保留*destAddr = object;但源代码似乎不会保留它。我真的很困惑。

我获得了Block Runtime here的源代码,您可以使用obj获取它。

更新1:

svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt

3 个答案:

答案 0 :(得分:3)

是和否。它没有直接"保留"它。我会解释一下。

obj是一个__block变量。因此,该块在某种程度上保持了一个参考"变量obj,而不是其他变量的副本。

你说块"保留"捕获的对象指针变量,因为块保留了捕获的常规(非__block)变量的内部副本,并且保留强对象指针变量的新副本需要保留它。

但是,在这种情况下,因为它是__block,所以只有一个变量副本。块(或块可能)和变量的原始范围都共享对变量的同一副本的访问。该块没有变量的独立副本。那么为什么块会保留它指向的对象呢? (并且假设如果块保留了它,并且一个块改变了变量指向的对象,那么所有其他块如何知道释放旧对象并保留新对象?)

(注意__block变量从堆栈开始并移动到堆,类似于块,但这种优化对于内存管理讨论并不重要,因为总是只有一个主动副本。可变的。)

另一种思考方式是__block变量真的表现得好像它们被包裹在某种透明的#34持有者中。对象实现共享状态。 "持有人" object将包装变量保存为内部字段,如果它是object-pointer类型,它将保留包装变量。但是,使用此变量的块(或块)仅包含对" holder"的引用。对象,而不是包装变量本身。对变量的所有访问都是通过引用" holder"来间接的。宾语。因此,当复制块时,它们将保留"持有者"对象,但不是里面的变量。当所有使用此__block变量的块都被释放时,则不会引用" holder"对象,以及"持有者"的析构函数。如果变量属于对象指针类型,对象将依次释放变量。

所以参考图如下:

block --> holder object --> NSObject object

该块间接保留对象,但不是直接保留。

更新:因此,您似乎想知道为什么,如果"持有者对象"将底层指针变量视为强引用,您不会看到" holder对象"分别在__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131中保留和释放基础变量。

实际上,如果你编译代码,你会看到不同的东西。运行clang -S -emit-llvm -fobjc-arc Block.m,您将获得文本LLVM IR,其中包括" holder对象"的以下副本和配置帮助程序:

; 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以强烈地(即使用retain)将持有者对象的堆栈版本中的变量值分配给堆版本中的变量。 holder对象,并执行另一个objc_storeStrong强烈地(即释放之前的那些)nil赋予持有者对象的堆栈版本中的变量。从本质上讲,它是以下代码在ARC中的作用:

heap_holder->var = stack_holder->var;
stack_holder->var = nil;

__Block_byref_object_dispose_函数中,它执行objc_storeStrong强烈地将nil赋予(即之前发布的内容)到持有者对象的堆版本中的变量。从本质上讲,它是以下代码在ARC中的作用:

heap_holder->var = nil;

这与" rewriter"生成的代码中的内容非常不同。我猜测重写器可能不考虑ARC - 换句话说,它执行MRC - > MRC重写。如果这是MRC - > MRC重写,生成的代码确实是正确的,因为__block变量永远不会保留在MRC中,即使它们是对象指针类型。 (还有其他证据表明这不是一个正确的ARC - > ARC重写。例如,对allocinit的调用只是被重写为对objc_msgSend的调用。但是{{ 1}}是ARC中的alloc方法,而ns_returns_retained不是objc_msgSend,因此将前者翻译为后者会导致保留不匹配,导致他们无法补救。但如果这样做是一个MRC - > MRC重写,这没关系,因为用户负责显式调用保留/释放。)

事实上,如果您在没有ARC的情况下再次使用ns_returns_retained进行编译,您会发现clang -S -emit-llvm Block.m__Block_byref_object_copy_函数确实使用了__Block_byref_object_dispose__Block_object_assign ,分别匹配您重写的代码显示的内容。

如果我们看看Clang源代码生成块代码,那么在构建byref帮助器的部分CodeGenFunction::buildByrefHelpers,会有一个if(第1970行),它检查变量是否有&# 34;寿命" (我认为这意味着它是ARC下的托管类型),如果是,则使用_Block_object_disposeARCWeakByrefHelpersARCStrongByrefHelpers构建;但如果它没有生命周期,则使用ARCStrongBlockByrefHelpers进行构建。以ARCStrongByrefHelpers为例,我们看到它在复制助手中发出两个存储强项,在dispose帮助器中发出一个destroy-strong,这是我们在ARC编译代码中看到的。另一方面,在ObjectByrefHelpers中,我们看到它在dispose帮助器中的复制助手和块释放中发出了block-object-assign,这就是我们在MRC编译代码中看到的。

但是如果你看一下Objective-C重写器的来源,生成byref副本和处理助手的方法RewriteModernObjC::SynthesizeByrefCopyDestroyHelper总是生成ObjectByrefHelpers_Block_object_assign。因此,这符合重写者仅进行MRC重写的假设,尽管我在重写器上找不到任何文档,所以我不能说这是否是设计的。 an answer另一个问题表明ARC在代码级别运行,这可能是源到源重写器不考虑它的原因(?)。

答案 1 :(得分:1)

我认为您在分析中缺少的是您没有看到为ARC插入的呼叫,而是您正在查看语言级别的分配。

__Block_byref_obj_0中,您将字段obj显示为:

NSObject *obj;

没有任何明确的所有权限定符。当我运行类似的测试时,Clang输出:

NSObject *__strong obj;

包括显式所有权限定符。

如果查看汇编级代码,您会看到在各个地方都有调用ARC内存管理例程。

所以你所看到的简单作业可能实际上被编译成一个强大的商店 - 即释放任何现有的参考,保留新的参考。这当然与原始Objective-C完全相同,您将ARC语义“读入”作为语言语义的一个组成部分。

HTH

答案 2 :(得分:1)

感谢@CRD和@newacct的建议,我分成汇编代码并找到一些线索。我将在此处发布汇编代码并进行一些分析。

汇编代码目标是armv7,因此long和指针需要4个字节来提供信息。

我的第一个目标是找到我的问题中引用的__Block_byref_id_object_copy函数,用于处理块被复制到堆的NSObject *obj,它位于__Block_byref_obj_0,让&# 39; s审查结构。

让我们使用"持有者对象"而不是__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位于持有者对象+ 16的地址,obj位于持有者对象+ 24的地址。

现在我在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_.2init方法,返回对象在r0中,然后r0存储在sp+104中,所以我知道__Block_byref_id_object_copy必须存储在{{1}它是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_中,r0存储目标持有者的地址,r1存储源持有者的地址。

___Block_byref_object_copy_

将r0存储到sp + 8,将r1存储到sp + 4,将r0移动到r1。所以r1 = r0 = dst持有人的地址。

str r0, [sp, #8]
str r1, [sp, #4]
ldr r0, [sp, #8]
mov r1, r0

然后它将r1 + 24添加到r1,所以r1将obj的地址存储在dst holder中,将[sp + 4]的值加载到r3,所以r3存储src持有者的地址,将r3移到r9,将r9 + 24添加到r9,因此r9在src holder中具有obj的地址。

adds    r1, #24
ldr r3, [sp, #4]
mov r9, r3
add.w   r9, r9, #24

它将[r3 + 24]中的值加载到r3,因此r3将obj存储在src持有者中,将r2存储到r0 + 24,r2为零,因此dst holder中的obj为NULL。然后将r1移到r0,r0在dst holder中有obj的地址,将r3移到r1,所以r1将obj存储在src持有者中。

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 是什么,请检查here

objc_storeStrong调用时,r0是dst holder中obj的地址,r1是src持有者中的obj。 bl _objc_storeStrong会将对象保留在src持有者中,并将其分配给dst持有者中的obj。

然后将0分配给r1,将[sp]加载到r0,[sp]将obj的地址存储在src holder中。

objc_storeStrong正在执行bl _objc_storeStrong,因此obj_src_holder将被发送释放方法并分配给nil。

结论:我不知道为什么它会在源代码持有者中释放obj并分配给nil,不应该在objc_storeStrong(&obj_src_holder, nil)结束时发布?

然而,目标持有者的obj确实强烈提及func(),所以这应该是我认为合理的答案。