3

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

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

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

这个错误是什么意思?

4

2 回答 2

8

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

- (int *) badMethod
{
   int aLocalIntVariable;

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

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

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

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

<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 查询的,结果是长长的问答评论交流。为了使信息更容易理解,我们都删除了我们的评论,我将这里的信息合并为附录。

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

  1. Objective-C 自动引用计数,特别是第 3 节(块是可保留对象指针)、3.2.3(可保留对象类型在返回边界上有效)和 7.5(块被复制时的规则)。
  2. 过渡到 ARC 发行说明,特别是常见问题解答项目“块如何在 ARC 中工作?”

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

第二个参考文献强调了一种情况,即至少在编写文档时没有自动处理。这种情况是堆栈分配的块被传递给类型的方法参数id,例如:

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

[obj takeBlockWithTypeLoss:^{ ... }];

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

这种情况是类型丢失的示例,已知是块类型的值作为id丢失类型信息传递。编译器总是可以确定这些点,那么为什么(或做过..)ARC 不复制该块?过去给出的答案只是效率之一,可能不需要副本,并且大量不需要的副本会影响性能。

然而,当前的编译器(Xcode 4.6.1)似乎可以处理剩下的这种情况,在类型丢失时,一个块被复制到堆中。如果有人可以证明这已记录在案(或者您确信您的编译器会处理这种情况,例如通过编写检查代码),那么它会出现Block_copy()(或[block copy])可以归入历史记录,如果没有,那么当发生类型丢失时应该使用它。

附录:2013 年 6 月

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

因此,存在当前编译器无法处理的情况,这表明编译器支持超过规范的原因是未记录的 - 支持不完整(尽管这些不是理论上的理由)。

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

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

于 2013-03-14T20:38:51.927 回答
7

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

即类似的东西:

dispatch_block_t createPrintBlock (const char *msg) {
    return Block_copy(^{
        printf("%s", msg);
    }) ;
}
于 2013-03-14T20:04:42.653 回答