混淆:__block NSObject *obj 和 block runtime

3

我使用 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->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 *)&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函数,该函数为__Block_byref_obj_0分配了一段内存,并进行了一些赋值操作,最后它将调用(*src->byref_keep)(copy, src),该指针指向__Block_byref_id_object_copy_131。正如我们在这个函数中所看到的那样,只有一行代码:_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;,没有保留引用!!!
我认为应该对obj进行保留,但源代码似乎没有对其进行保留。我真的很困惑。
您可以在这里获取Blocks Runtime的源代码,使用svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt命令即可。
static void _Block_assign_default(void *value, void **destptr) {
    *destptr = value;
}
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;

你认为为什么应该保留obj?如果保留它,然后将对它的唯一引用设置为nil,那么它不会泄漏吗? - danh
@danh 我读了许多文章来支持我的观点,这篇是其中之一。 - KudoCC
考虑一下强制设置 obj=nil 的作用:释放旧值(在您的代码中是 obj),分配新值(在您的代码中是 nil),保留新值(在您的代码中是 [nil retain])。如果您的信念是正确的,那么这将按设计工作,并且会泄漏。再次生成代码,这次除了分配给 nil 之外,尝试其他任何操作。 - danh
@danh 抱歉,但是泄漏了什么?你不觉得该块会对 obj 保持强引用吗? - KudoCC
在块之前分配的对象(称为 obj)。它被块保留,因为块引用它(而不是因为 __block 修饰符)。代码将其设置为 nil,因此需要释放。如果将其设置为 nil 没有释放它,那么它将泄漏。 - danh
如果我省略 __block,标志将是“BLOCK_FIELD_IS_OBJECT”而不是“BLOCK_FIELD_IS_BYREF”,因此将调用“_Block_retain_object(object);”。将 obj 设置为 nil 将释放它,因此不会泄漏。 - KudoCC
3个回答

3
是和否。它并不直接“保留”它。我来解释一下。 obj 是一个 __block 变量。因此,块以某种方式拥有对变量 obj 的“引用”,而不像其他变量那样拥有它的副本。
你说块会“保留”捕获的对象指针变量,这是因为块会保留捕获的常规(非 __block)变量的内部副本,并且保留强对象指针变量的新副本需要对其进行保留。
然而,在这种情况下,由于它是 __block,所以只有一个变量副本。块(或者可能是多个块)和变量的原始作用域都共享访问同一个变量副本。块没有独立的变量副本。那么,为什么块会保留它所指向的对象呢?(想象一下,如果块保留它,而一个块更改了变量所指向的对象,所有其他块怎么知道释放旧对象并保留新对象呢?)
(注意,__block变量最初在堆栈上启动,然后移动到堆上,类似于块,但是这种优化对于内存管理讨论并不重要,因为变量始终只有一个活动副本。)
另一种思考方式是,__block变量实际上表现得好像它们被包装在某种透明的“持有者”对象中以实现共享状态。 “持有者”对象将包装变量保留为内部字段,并且如果它是对象指针类型,则保留包装变量。但是,使用此变量的块(或块)仅持有对“持有者”对象的引用,而不是对包装变量本身的引用。所有对变量的访问都是通过对“持有者”对象的引用间接进行的。因此,当复制块时,它们将保留“持有者”对象,但不保留其中的变量。当使用此__block变量的所有块都被解除分配时,就不再有对“持有者”对象的引用了,然后“持有者”对象的析构函数将依次释放其中的变量,如果它是对象指针类型。
因此,参考图如下:
block --> holder object --> NSObject object

这个块间接地保留了对象,但并不是直接保留。


更新:所以,如果“holder object”将底层指针变量视为强引用,为什么您在__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131中没有看到“holder object”保留和释放底层变量,那么似乎您很想知道。

实际上,如果您编译代码,您会看到不同的东西。运行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来将堆版本的持有者对象变量的值强制分配(即保留)到栈版本的持有者对象变量中,并执行另一个objc_storeStrong,将nil强制分配(即释放之前的内容)到持有者对象栈版本的变量中。本质上,这就是在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重写。例如,调用allocinit只是被重写为调用objc_msgSend。但alloc是一个在ARC中使用ns_returns_retained方法,而objc_msgSend并不是ns_returns_retained,所以将前者翻译成后者会导致一种保留不匹配的情况,他们无法补救。但如果这是一个MRC->MRC重写,这样做就没问题,因为用户需要显式地调用retain/release。)

确实,如果您使用 clang -S -emit-llvm Block.m 重新编译而没有启用ARC,您会发现__Block_byref_object_copy___Block_byref_object_dispose_函数确实使用了_Block_object_assign_Block_object_dispose,这与您重写的代码所显示的相匹配。

如果我们查看Clang源代码中的块代码生成部分,构建byref helper的部分CodeGenFunction :: buildByrefHelpers,其中有一个if语句(第1970行),它检查变量是否具有“生命周期”(我认为这意味着它是在ARC下管理的类型),如果是,则使用ARCWeakByrefHelpersARCStrongByrefHelpersARCStrongBlockByrefHelpers进行构建;但如果没有生命周期,则使用ObjectByrefHelpers进行构建。例如,查看ARCStrongByrefHelpers,我们可以看到它在复制helper中发出两个store-strong,并在dispose helper中发出destroy-strong,这正是我们在ARC编译的代码中看到的。另一方面,在ObjectByrefHelpers中,我们可以看到它在复制helper中发出block-object-assign,在dispose helper中发出block-release,这正是我们在MRC编译的代码中看到的。
但是,如果你查看Objective-C重写器的源代码,生成byref copy和dispose helpers的方法RewriteModernObjC::SynthesizeByrefCopyDestroyHelper总是生成_Block_object_assign_Block_object_dispose。因此,这符合重写器只对MRC进行重写的假设,尽管我找不到有关重写器的任何文档,所以我不能确定这是否是有意设计的。还有另一个问题的答案表明ARC在代码生成层面操作,这可能是源到源重写器不考虑它的原因之一(?)。

谢谢你的回答。但是源代码显示堆中的“holder object”没有对obj的强引用,我知道这是因为_Block_assign只调用_Block_assign_default,而_Block_assign_default只进行了一个简单的赋值,就像我在update1中展示的那样。 - KudoCC
不,方法__Block_byref_id_object_copy_131调用了_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);(char *)src + 40__Block_byref_obj_0NSObject *obj;的地址。 - KudoCC
@KudoCC:但是__Block_byref_id_object_copy_131只是将“持有对象”从栈移到堆中,并不需要对其中的变量做任何操作,因为在之前和之后只存在一个有效版本。 - newacct
但是如果堆中的 obj 没有保留对象,当栈持有者超出其范围时,栈持有者中的 obj 将被释放。 - KudoCC
@KudoCC:嗯,进一步看来,我认为__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131的生成代码是错误的。在编译后的代码中,有___Block_byref_object_copy____Block_byref_object_dispose_。在___Block_byref_object_copy_中,没有_Block_object_assign;相反,它调用objc_storeStrong将堆栈持有者中的obj强制(保留)分配给堆持有者中的obj,然后再次调用objc_storeStrongnil分配给堆栈持有者中的obj(释放)。 - newacct
显示剩余10条评论

2
感谢@CRD和@newacct的建议,我将代码分解为汇编代码并找到了一些线索。我会在此处发布汇编代码并进行一些分析。
该汇编代码的目标是armv7,因此长整型和指针占用4字节,供您参考。
我的第一个目标是找到__Block_byref_id_object_copy函数,它被引用于我的问题中,并且用于处理NSObject *obj,在块被复制到堆上时使用,它位于__Block_byref_obj_0中,请让我们回顾一下结构体。
为了简化,让我们使用“持有者对象”代替__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处。

现在我贴出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必须存储在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_是相同的,我不知道编译器背后做了什么,但它确实更改了函数名称,不要紧,我需要知道的是第一个参数是目标持有者对象的地址,第二个参数是源持有者对象的地址。
因此,在___Block_byref_object_copy_中,r0存储目标持有者的地址,r1存储源持有者的地址。
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 holder的地址。
adds    r1, #24
ldr r3, [sp, #4]
mov r9, r3
add.w   r9, r9, #24

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

ldr r3, [r3, #24]
str r2, [r0, #24]
mov r0, r1
mov r1, r3
str.w   r9, [sp]

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

bl  _objc_storeStrong
movs    r1, #0
ldr r0, [sp]
bl  _objc_storeStrong

要了解什么是objc_storeStrong,请查看此处
当调用bl _objc_storeStrong时,r0是dst holder中obj的地址,r1是src holder中的obj。objc_storeStrong将在src holder中保留obj,并将其分配给dst holder中的obj。
然后它将0分配给r1,将[sp]加载到r0中,[sp]存储src holder中的obj的地址。 bl _objc_storeStrong执行了objc_storeStrong(&obj_src_holder, nil),因此obj_src_holder会发送release方法并被分配为nil。
总之:我不知道为什么要释放源holder中的obj并赋值为nil,难道它不应该在func()结束时释放吗?
然而,目标holder中的obj确实保留了对NSObject *obj的强引用,所以我认为这应该是一个合理的答案。

“我不知道为什么它会在源持有者中释放obj并赋值为nil,难道不应该在func()结束时释放吗?”嗯,这可以用两种方式来完成。在将obj移动到堆之后,堆栈持有者中的obj将不再使用,因此无论我们是立即将其置为nil还是等待其超出范围,都没有关系。 - newacct

1
我认为你在分析中忽略了ARC插入的调用,而是只看到了语言级别的赋值。
__Block_byref_obj_0中,你展示了obj字段:
NSObject *obj;

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

NSObject *__strong obj;

包括明确的所有权限定符。

如果您查看汇编级别的代码,您会发现在各个地方插入了对ARC内存管理例程的调用。

因此,您所看到的简单赋值实际上可能被编译为强制存储——即释放任何现有引用,保留新引用。当然,这与原始的Objective-C完全相同,您将“读取”ARC语义作为语言语义的组成部分。

希望对您有所帮助。


我同意你的观点。我可以这样说,编译器在插入ARC代码之前将OC翻译成C++,而runtime.c文件中的代码(该文件位于_Block_object_assign)也会在编译时插入retainrelease吗? - KudoCC
你知道如何重写支持ARC的OC文件为C++文件吗? - KudoCC
我不知道有任何选项可以在ARC分析之后生成C++代码。你可以通过使用“-rewrite-object”从C++源码中读取与读取Objective-C代码相同的方式,来阅读具有ARC语义的代码。或者你可以查看汇编输出,那里会显示所有的ARC调用。 - CRD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接