*应该*崩溃的简单Objective-C过度释放不会崩溃。为什么?

问题描述:

我的调试器坏了,或者有一些根本不了解。*应该*崩溃的简单Objective-C过度释放不会崩溃。为什么?

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

int main (int argc, const char * argv[]) 
{ 
    NSString *string = [[NSString alloc] initWithString:@"Hello"]; 

    [string release]; 

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

    return 0; 
} 

日志语句按照您对有效字符串的期望值打印“Length: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对应的桥接。这可能与它有关吗?

+0

前段时间我一直在想自己,看到了同样的'问题'/行为。希望有人能解释这一点。 – Rits 2010-11-02 15:33:19

+0

垃圾收集不是即时的? :) – willcodejavaforfood 2010-11-02 15:42:19

+1

这不是重点。在垃圾收集环境中,显式调用'release'完全没有任何作用。 – Yuji 2010-11-02 15:48:37

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

在这种情况下,

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

仅返回相同的对象@"Hello"作为优化。常数NSString永远不会被释放;作为优化,retainrelease(我认为)被忽略。这就是为什么它不会崩溃。

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

+0

好的,这对我来说很有意义。如果我做了[[NSString alloc] init];那么与使用常量字符串进行初始化相反呢?在我的测试中,它仍然表现出与使用常量字符串相同的行为,这也是预期吗?而其他情况下,我看到相同的行为,比如'NSURL'和'NSNumber'? – Jasarien 2010-11-02 15:37:52

+0

是的,你是正确的,字符串和@“你好”都指向相同的位置。这清除了这一点。尽管如此,我仍然对其他案例感到困惑。 – Jasarien 2010-11-02 15:42:48

+0

''[[NSString alloc] init]'返回一个共享的空字符串,这个字符串永远不会被销毁。不能保证每次调用'[[SomeClass alloc] init]'都会给出一个独特的实例。合同是如果你遵循保留/发布规则,程序不会崩溃。没有什么更多。 – Yuji 2010-11-02 15:46:01

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

尝试

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

这应该给你一个字符串,它会表现得更像你期待,因为你不是从一个编译时间常数初始化您的字符串的方式。但是,要注意的是,在启用僵尸的情况下,你会得到你期望的行为,但没有僵尸,NSLog很可能会工作。虽然该字符串已被释放,但对象中的数据仍然存在于内存中,留下一个“鬼影”,可以正确响应某些消息。