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

Ros*_*one 3 objective-c objective-c-blocks

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

dispatch_block_t getPrintBlock (const char *msg) {
    return ^{
        printf("%s", msg);
    };
}
Run Code Online (Sandbox Code Playgroud)

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

这个错误是什么意思?

CRD*_*CRD 8

该错误意味着您返回的值在方法返回后将无效.这不仅仅是块的问题,请考虑:

- (int *) badMethod
{
   int aLocalIntVariable;

   return &aLocalIntVariable; // return a reference to aLocalIntVariable, but that variable is about to die...
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是,Objective-C为您提供了两种方法来创建块值的副本作为对象,它存在于"堆"中,并且将比其创建者更长久.首先是Block_copy功能:

<reference to heap allocated block> = Block_copy(<reference to stack allocated block>);
Run Code Online (Sandbox Code Playgroud)

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

<reference to heap allocated block> = [<reference to stack allocated block> copy];
Run Code Online (Sandbox Code Playgroud)

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

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

附录:ARC

@newacct查询了上面的最后一段,并进行了长时间的问答评论.为了使信息更易于理解,我们已经删除了我们的意见,并将此信息作为附录进行了整合.

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

  1. Objective-C自动引用计数,特别是第3节(块是可保留的对象指针),3.2.3(可保留的对象类型在返回边界上有效)和7.5(块复制时的规则).
  2. 转换到ARC发行说明,特别是FAQ项目"块如何在ARC中工作?"

从这些可以确定,大多数时候 ARC将处理从堆栈到堆的所有块的复制,作为其管理所有对象类型的一部分.

第二个参考突出显示了一个案例,至少在编写文档时,没有自动处理.这种情况是将堆栈分配块传递给类型的方法参数id,例如:

- (void) takeBlockWithTypeLoss:(id)block { ... }

[obj takeBlockWithTypeLoss:^{ ... }];
Run Code Online (Sandbox Code Playgroud)

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

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

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

附录:2013年6月

正如这个问题所揭示的那样,Xcode 4.6.3/Clang 4.2 无法处理.当块作为变量参数之一传递给可变参数方法时,编译器不会自动将堆栈块提升到堆.这是上面提到的类型丢失的子例子.

因此,当前编译器无法处理这种情况,这表明支持多于规范的编译器没有记录的原因 - 支持是不完整的(尽管这些理论上并不是理论上的原因).

和以前一样,如果存在类型丢失,那么编译器可能不会自动处理块提升(但是如果需要可以测试它),不涉及类型丢失的情况将根据规范自动处理.

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


nie*_*bot 6

您需要复制块以将其移动到堆中.

即:像:

dispatch_block_t createPrintBlock (const char *msg) {
    return Block_copy(^{
        printf("%s", msg);
    }) ;
}
Run Code Online (Sandbox Code Playgroud)