返回生活在本地堆栈上的块

时间:2013-03-14 20:02:46

标签: objective-c objective-c-blocks

clang分析器可以检查返回的基于堆栈的内存。

dispatch_block_t getPrintBlock (const char *msg) {
    return ^{
        printf("%s", msg);
    };
}

引发此错误:returning block that lives on the local stack

这个错误是什么意思?

2 个答案:

答案 0 :(得分:8)

错误表示您返回的值在方法返回后将无效。这不仅仅是块的问题,请考虑:

- (int *) badMethod
{
   int aLocalIntVariable;

   return &aLocalIntVariable; // return a reference to aLocalIntVariable, but that variable is about to die...
}

在输入方法时会创建局部变量,它们所在的位置称为" stack"。当方法返回时,那些局部变量被销毁。您可以在此类变量中返回,但不能将引用返回给变量本身 - 它将无效。您可以将对局部变量的引用传递给您调用的方法,因为在这种情况下您的局部变量仍然存在。

在您的情况下,您创建了一个块。 Objective-C恰好在堆栈上创建块值,即在匿名局部变量中有效,并使用引用引用它们。你可以将这样的引用传递给你调用的方法,但你不能返回它 - 匿名局部变量就像其他任何一样被销毁。

然而,Objective-C为您提供了两种方法来创建块值的副本作为对象,它存在于"堆",并且将比其创建者寿命更长。首先是Block_copy这是一个函数:

<reference to heap allocated block> = Block_copy(<reference to stack allocated block>);

这是执行此操作的原始方式,并且每个都支持 - 包括在纯C代码中,块是C的一部分而不仅仅是Objective-C。 假装块的第二种方式已经是一个对象,并允许您发送标准的copy消息:

<reference to heap allocated block> = [<reference to stack allocated block> copy];

如果你主要是一个Objective-C人,那么第二种方法可能会感觉更舒服,但确实首先模糊了为什么需要它的问题。

ARC有助于自动化内存管理,它会自动将块从堆栈复制到堆中(至少在当前的编译器中,它可能在早期的编译器中无法正常工作),因此程序员可以忽略实际的实现细节

附录:ARC

@newacct查询了上面的最后一段,并进行了长时间的Q&amp; A评论交流。为了使信息更容易理解,我们都删除了我们的意见,并在此作为附录整理了这些信息。

在理解ARC如何处理块时,两个文档很有用:

  1. Objective-C Automatic Reference Counting,特别是第3节(块是可保留的对象指针),3.2.3(可保留的对象类型在返回边界上有效)和7.5(复制块时的规则)。
  2. Transitioning to ARC Release Notes,特别是FAQ项目&#34;块如何在ARC中工作?&#34;
  3. 从中可以确定大部分时间 ARC将处理从堆栈到堆的所有块的复制,作为其管理所有对象类型的一部分。

    第二个参考文献突出显示了一个案例,至少在撰写文档时,并未自动处理。这种情况是将堆栈分配的块传递给类型为id的方法参数,例如,类似的东西:

    - (void) takeBlockWithTypeLoss:(id)block { ... }
    
    [obj takeBlockWithTypeLoss:^{ ... }];
    

    在这种情况下,在编写文档时,ARC没有复制该块。如果被调用的方法然后执行保留传递的块的操作,则由于保留值不在堆上而发生问题。 (请注意,块需要为要发生的问题分配堆栈。在其环境中不引用变量的文字块是静态分配的,也是首先存储在具有默认强所有权的本地变量中的文字块。然后传递给方法将被复制。

    这种情况是类型丢失的一个例子,已知为块类型的值作为id丢失类型信息传递。编译器总是可以确定这些点,那么为什么(或者......)ARC没有复制块?过去给出的答案只是效率之一,副本可能不需要,许多不需要的副本都是性能损失。

    然而当前编译器(Xcode 4.6.1)出现以处理剩下的这种情况,在类型丢失时,块被复制到堆中。如果任何人都可以证明这已经记录下来了(或者你确信你的编译器处理这种情况,例如通过编码检查)那么它会显示Block_copy()(或[block copy])可以降级到历史记录,如果不是然后当发生类型丢失时,应该使用它。

    附录:2013年6月

    正如this question所揭示的,有一种情况是Xcode 4.6.3 / Clang 4.2做处理。当块作为变量参数之一传递给可变参数方法时,编译器不会自动将堆栈块提升到堆。这是上面提到的类型丢失的一个子例子。

    因此有一个案例当前编译器无法处理,这表明支持多于规范的编译器没有记录的原因 - 支持是不完整的(尽管这些不是它需要的理论原因)。

    与以前一样,如果存在类型丢失,则编译器可能无法自动处理块提升(但如果需要,可以对此进行测试),不涉及类型丢失的情况将根据规范自动处理。

    (顺便说一下。对上述问题的评论中提到的older question现在是规范所涵盖的案例之一,并由编译器正确处理。)

答案 1 :(得分:6)

您需要制作块的副本才能将其移动到堆中。

即。类似的东西:

dispatch_block_t createPrintBlock (const char *msg) {
    return Block_copy(^{
        printf("%s", msg);
    }) ;
}