【问题标题】:Inconsistent object deallocation with ARC?与ARC不一致的对象释放?
【发布时间】:2012-02-28 16:42:00
【问题描述】:

我正在使用启用 ARC 的 Xcode 4.2.1 版和默认构建设置构建的 Mac OSX 10.7 的简单命令行应用程序上玩内存(取消)分配的东西。我无法解释从我对 ARC 的了解中得到的行为,所以我希望有人能解释这里发生了什么。

首先,在下面的代码中,我得到了我期望的行为(请注意,NLog() 输出在相应语句之后的注释中给出)

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __weak NSObject *weakRef = objPtr1;
    NSLog(@"%@", [objPtr1 description]); // <NSObject: 0x1001107d0>
    objPtr1 = nil;
    NSLog(@"%@", [objPtr2 description]); // <NSObject: 0x1001107d0>
    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // (null)
    return 0;
}

所以在上面,在分配了weakRef之后,NSObject实例有两个指向它的强指针,因此保留计数为2。在将objPtr1归零后,仍然有一个指向实例的保留指针,所以它仍然在内存中并且响应描述消息。在 nil-ing objPtr2 之后,没有指向该对象的强指针并且它被释放(我假设它是,因为 weakRef 已被归零)。到目前为止,一切顺利。

现在,相同的代码稍作改动:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __unsafe_unretained NSObject *weakRef = objPtr1; // __unsafe_unretained instead of just __weak
    NSLog(@"%@", [objPtr1 description]); // <NSObject: 0x1001107d0>

    objPtr1 = nil;
    NSLog(@"%@", [objPtr2 description]); // <NSObject: 0x1001107d0>

    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>
    //why was the object instance not deallocated and the preceding statement not crash the program?
    return 0;
}

我原以为weakRef会变成一个悬空指针,通过它发送一条消息会导致程序在第三个NSLog()语句中崩溃,但似乎对象实例还活着并且很好。

我觉得奇怪的另一件事:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{

    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __weak NSObject *weakRef = objPtr1; // __weak again
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    objPtr1 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    return 0;

}

最后一个代码和第一个代码一样(使用归零的 __weak 指针);唯一的区别是在三个 NSLog() 调用中的每一个中,描述消息都是通过weakRef 发送到对象的。但是这一次,即使删除了两个强引用,对象也没有被释放(因为它仍然通过weakRef响应消息)。

那么这里发生了什么?

【问题讨论】:

  • 好的,如果我在“objPtr2 = nil”和“NSLog(@ "%@", [weakRef description])" 程序有时会崩溃(出现 EXC_BAD_EXCESS 错误),有时不会。如果我插入另一个填充 NSLog() 语句,那么程序肯定会崩溃(至少在我运行它的所有时间)。所以我想这意味着回收释放的内存不一定是对象丢失所有强引用后发生的绝对第一件事。
  • 代码 #3 对我来说仍然是一个谜,但是......如果我通过代码中的 weakRef 向我们的实例发送消息,那么即使在所有对它的强引用被删除了——甚至当 main() 返回时也没有(我将 NSObject 子类化并覆盖了 dealloc 方法,放入 NSLog() 以检查它是否会被调用——它不是)。似乎它可能是一个错误。
  • 这确实很奇怪,是的。你对代码 #2 是正确的 - 内存还没有被重新使用,所以它仍然指向一个“有效的”NSObject,所以description 工作正常。代码#3 很奇怪。我注意到如果将第二个description 发送到weakRef,它不会为零weakRef。即,如果您将description 发送到weakRef,然后发送objPtr2,然后发送weakRef,它会正确输出最后一个(null)
  • 事实上,您只需要在看起来的对象上调用 2 个方法。大概这里有某种错误。我没有时间再打猎,但我会在其他时间进一步看看。一个有趣的!
  • 刚刚添加了一个更精简版本的代码 3 的答案,我认为它显示得更好一些。我认为这是一个错误,所以我们可能应该提交雷达。

标签: objective-c automatic-ref-counting


【解决方案1】:

如果你反汇编 A.R.C. 生成的代码,每次对弱变量的访问都包含在对该函数的调用中:

id objc_loadWeak(id *location)
{
    return objc_autorelease(objc_loadWeakRetained(location));
}

这会检查对象是否已经被释放,如果没有,则保留并自动释放它,以防止过早释放。

因此,在您的第三个示例中,对 weakRef 方法的早期调用导致其保留计数增加,因此将指针归零不会导致它被释放。

【讨论】:

  • 确实是这样,但奇怪的是行为会随着对象上方法的调用次数而不同,甚至会随着优化级别的不同而不同。因此,我们正在做的似乎是未定义的行为。但是我真的不明白为什么查看文档是未定义的。您能否参考文档说明这一点?
【解决方案2】:

这看起来确实很奇怪。你是对的(在你的 cmets 中)关于第二位代码只是因为内存还没有被重用。但第三位代码是陌生的。这是一个更简化的测试用例,它显示了这个奇怪的问题:

#import <Foundation/Foundation.h>

@interface SomeClass : NSObject 
@end

@implementation SomeClass
- (void)foo {
}
@end

int main (int argc, const char * argv[]) {
    @autoreleasepool {
        SomeClass *objPtr1 = [[SomeClass alloc] init];
        __weak SomeClass *weakRef = objPtr1;

//        [weakRef foo];
        [weakRef foo];

        objPtr1 = nil;

        NSLog(@"%p", weakRef);

        return 0;
    }
}

将该行注释掉后,输出为:

$ clang -fobjc-arc -framework Foundation test.m -o test -O3 && ./test
2012-02-12 00:39:42.769 test[6684:707] 0x0

没有注释掉那一行,输出是:

$ clang -fobjc-arc -framework Foundation test.m -o test -O3 && ./test
2012-02-12 00:42:04.346 test[6688:707] 0x100f13f50

这看起来很奇怪,在我看来完全像一个错误。我实际上不知道答案是什么,但我想我会把这个作为答案发布,以便弄清楚发生了什么。

更新:

如果您在O0 上构建它,那么似乎只有当没有 调用fooweakRef 才被归零。对foo 的一次调用将意味着它不会归零。

【讨论】:

  • 移除自动释放池,当 objPtr1 被多次调用或其弱引用时,它看起来像被分配为自动释放。这似乎是一种有意的优化,即使在实例化后仅立即调用一次时,也不会将强引用添加到池中。
  • 事实上,当使用 -O0 编译时,weakRef 甚至没有归零,无论有没有自动释放池。
  • @jshier 我在-O0 看到,如果我不调用fooweakRef 将归零,但如果我只调用一次,则它不会归零。
  • 我感觉这 - clang.llvm.org/docs/… - 在这里是相关的,但我的大脑并没有让我足够简洁地解析这些信息以让我的头脑了解它。还有其他人吗?
猜你喜欢
  • 2012-05-04
  • 2013-11-29
  • 2013-09-07
  • 2023-03-19
  • 2012-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多