【问题标题】:Memory management in local variable objective-c局部变量objective-c中的内存管理
【发布时间】:2013-07-19 09:54:13
【问题描述】:

在一次采访中,我被要求实现 NSArray 的 exchangeObjectAtIndex:withObjectAtIndex: 方法。 我写了以下代码:

- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
  id tmp = [self objectAtIndex:index1];
  [self replaceObjectAtIndex:index1 withObject:[self objectAtIndex:index2]];
  [self replaceObjectAtIndex:index2 withObject:tmp];
}

面试官说这是第一行的内存管理问题,我要赶上 bad_access_exc。 他建议这样写:

- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
    id tmp = [[[self objectAtIndex:index1] retain] autorelease];
    [self replaceObjectAtIndex:index1  withObject:[self objectAtIndex:index2]];
    [self replaceObjectAtIndex:index2 withObject:tmp];
}

我知道他的代码是正确的,但是由于 tmp 是局部变量并且它将被分配,所以没有释放,一切都会好起来的。有什么错误吗?

【问题讨论】:

    标签: objective-c memory memory-management


    【解决方案1】:

    如果您使用手动内存管理,则会出现错误。 Apple 在“Avoid Causing Deallocation of Objects You’re Using” in the Advanced Memory Management Programming Guide 下记录了该问题。

    具体来说,objectAtIndex: 不是 retainautorelease 它返回给您的对象。因此,NSArray 可能具有对该对象的唯一“拥有”引用。在手动保留计数 (MRC) 下分配给 tmp 不会保留对象,因此 tmp 不拥有它并且自动释放池不拥有它。

    这意味着当您的方法的第 2 行发送[self replaceObjectAtIndex:index1 withObject:[self objectAtIndex:index2]] 时,数组可能会释放对该对象的最后一个引用,从而释放它。此时,tmp 指的是一个已释放的对象;这称为“悬空引用”。

    然后在第 3 行,您尝试将悬空引用放入数组中。数组会将retain 发送给引用,这是无效的,您将崩溃或遇到堆损坏。

    在 ARC 下,分配给 tmp确实保留对象,因此在这种情况下不会出错。

    【讨论】:

    • 谢谢,解释得很清楚。但是现在我又有了一个误解:我创建了一个空项目并编写了代码posted in pastebin,它应该会失败但它运行良好。为什么?我已禁用弧,似乎没有任何其他链接到该数组。
    • Core Foundation 框架缓存了NSNumber 实例,用于从-112 的整数。一旦分配,这些实例永远不会被释放。在CFNumber.c中搜索MinCachedInt了解详情。
    • 静态分析器或僵尸程序有可能无论如何都会检测到您的错误。我没试过。
    • @RustamGaneyev - 不保证会失败。这取决于释放的存储是否被重用,以及有问题的对象是否保留在其他地方。例如,如果您制作了一个用文字字符串或文字 NSNumbers 填充数组的测试用例,那么它们很可能是“实习生”(使用 Java 术语),因此它们永远不会被删除。但现实生活会咬你一口。 (我曾经被同样的基本问题所困扰,只是在一些异步代码中,而且很难追查。)
    【解决方案2】:

    请记住,id tmp 只不过是指向数组中对象的指针。它没有说明它指向的对象的内存管理。

    ...它会被分配,所以没有释放...

    这是这里的症结所在。您不能保证将index1 的对象替换为index2 的对象时不会被释放。实际上,此时数组将在其上调用release,以平衡它在对象最初添加到数组时调用的retain。因此,当index1 处的对象被替换时,index2 处的对象可能会被替换,index1 处的对象的引用计数将变为零,该对象将被释放,而您的 tmp 变量将变成一个悬空指针。 ... retain] autorelease] dance 使对象保持足够长的时间来进行交换,而不必担心它在方法结束之前释放(它可能会一直停留到下一个运行循环的顶部)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-09
      • 2011-06-28
      • 1970-01-01
      • 2020-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多