*应该*崩溃的简单Objective-C过度发布不会崩溃。为啥?

Posted

技术标签:

【中文标题】*应该*崩溃的简单Objective-C过度发布不会崩溃。为啥?【英文标题】:Simple Objective-C over-release that *should* crash is not crashing. Why?*应该*崩溃的简单Objective-C过度发布不会崩溃。为什么? 【发布时间】:2011-05-04 00:18:38 【问题描述】:

要么我的调试器坏了,要么我不理解一些基本的东西。

我在一个非常基本的命令行程序中有一些非常基本的代码应该崩溃。但是,它并没有崩溃。

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

    NSString *string = [[NSString alloc] initWithString:@"Hello"];

    [string release];

    NSLog(@"Length: %d", [string length]);

    return 0;

日志语句打印“长度:5”,正如您对有效字符串的期望一样。但是,到那时字符串应该被释放并且应该抛出一个exec_bad_access 错误。

我已经在附加调试器和未附加调试器的情况下尝试了这段代码 - 两者都给出了相同的结果。我还启用(和禁用)NSZombie,这似乎没有任何效果(我最初认为这是问题所在,因为NSZombie 对象从未被释放 - 但它仍然不会因NSZombie 禁用而崩溃)。

我在我的本地 .gdbinit 文件中设置了断点来中断诸如 -[NSException raise]objc_exception_throw 之类的东西。我还在NSZombie 上的许多方法上设置了断点以捕获它们。

fb -[NSException raise]
fb -[NSAssertionHandler handleFailureInFunction:file:lineNumber:description:]
fb -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]

#define NSZombies
# this will give you help messages.  Set to NO to turn them off.
set env MallocHelp=YES
# might also be set in launch arguments.
set env NSZombieEnabled=YES
set env NSDeallocateZombies=NO
set env MallocCheckHeapEach=100000
set env MallocCheckHeapStart=100000
set env MallocScribble=YES
set env MallocGuardEdges=YES
set env MallocCheckHeapAbort=1

set env CFZombie 5

fb -[_NSZombie init]
fb -[_NSZombie retainCount]
fb -[_NSZombie retain]
fb -[_NSZombie release]
fb -[_NSZombie autorelease]
fb -[_NSZombie methodSignatureForSelector:]
fb -[_NSZombie respondsToSelector:]
fb -[_NSZombie forwardInvocation:]
fb -[_NSZombie class]
fb -[_NSZombie dealloc]

fb szone_error
fb objc_exception_throw

设置了这些断点并启用了 NSZombie,我应该会在控制台上打印出类似 [NSString length]: message sent to deallocated instance 0x100010d39 的内容,但我看不到这一点。我看到NSLog 将长度打印为 5。

我看到其他类(例如 NSURLNSNumber)也有类似的行为。但是有些类会按预期崩溃,例如NSErrorNSObject

这与类集群有关吗?他们在内存管理方面不遵循相同的规则吗?

如果类集群与此问题无关,那么我能看到的唯一其他共同特征是不会以这种方式崩溃的类都是与 Core Foundation 对应的免费桥接的。会不会跟这有关系?

【问题讨论】:

前段时间我一直在想自己,看到了同样的“问题”/行为。希望有人能解释一下。 垃圾回收不是即时的? :) 这不是重点。在垃圾收集环境中,对release 的显式调用绝对没有任何作用。 这不是垃圾回收环境 【参考方案1】:

retain/release 是 API 和程序员之间的契约,当你遵循规则时,它不会崩溃。合同不保证如果你不遵守规则,它确实会崩溃!

在这种情况下,

[[NSString alloc] initWithString:@"Hello"]

只返回与@"Hello" 相同的对象作为优化。常量NSString 永远不会被释放;作为优化,retainrelease(我认为)被忽略了。这就是它不会崩溃的原因。

你可以通过比较@"Hello"string的指针值来验证我的猜测。

【讨论】:

好的,这对我来说很有意义。如果我使用[[NSString alloc] init]; 而不是使用常量字符串进行初始化呢?在我的测试中,它仍然表现出与使用常量字符串相同的行为,这也是预期的吗?那么我看到相同行为的其他情况呢,例如NSURLNSNumber 是的,你是对的,string 和 @"Hello" 都指向同一个位置。这清楚了。不过,我仍然对其他情况感到困惑。 [[NSString alloc] init] 返回一个共享的空字符串,该字符串永远不会被破坏。不能保证每次调用 [[SomeClass alloc] init] 都会给出一个不同的实例。合同是,如果您遵循保留/释放规则,程序不会崩溃。仅此而已。 @Jasarien [[NSString alloc] init]@"" 短路。 WRT NSNumber,0-12 的数字是单例。虽然不确定NSURL... 对您创建的每个对象执行NSLog(@"%@",[anObject class]); 可能有助于您理解。您清楚地看到,该框架使用专门的类对非常常见的实例进行优化。【参考方案2】:

这是一个很好的例子,说明了为什么保留计数是一个非常无用的调试工具。假设 -retain 和 -release 总是在保留计数中添加或删除 1 并且保留计数是您认为应该的值,这是错误的。

试试

NSString *string = [[NSString alloc] initWithFormat:@"Hello %d", argc];

这应该会给你一个字符串,它的行为更像你所期望的,因为你没有从编译时间常量初始化你的字符串。但是,请注意,启用僵尸后,您将获得预期的行为,但没有僵尸,NSLog 可能会很好地工作。尽管字符串已被释放,但对象中的数据仍然存在于内存中,留下一个“幽灵”,它将正确响应某些消息。

【讨论】:

以上是关于*应该*崩溃的简单Objective-C过度发布不会崩溃。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C & EXC_BAD_ACCESS

在不使用自引用的情况下设置 Objective-C 类属性

Objective-C - 为啥添加两个字符串会导致崩溃?

具有 Objective-C 和 swift 代码的应用程序在发布模式下启动时崩溃,调试模式正常

写入后 NSOutputStream 因访问错误而崩溃(Objective-c)

Objective-C NSMutableArray removeObjectAtIndex:崩溃