访问已释放的NSString不会导致应用程序崩溃。

3

我知道这可能与Stackoverflow上的问题完全相反,但今天发生了一件非常奇怪的事情。

我试图向某人展示如何使用Instruments和NSZombies,所以我尝试制造一个崩溃。

我声明了一个NSString,释放了它,然后尝试访问它,但应用程序没有崩溃。

NSString* test = [[NSString alloc] init];
[test release];
NSlog(@"%@", test);

我甚至尝试了两次发布,但它仍然没有使应用程序崩溃,只是打印出null。

请问有人能解释一下我做错了什么或我的逻辑存在哪些缺陷吗?

谢谢


编辑:我也尝试过类似的操作,但仍然没有崩溃。

NSString* test = [[NSString alloc] init];
test = @"something";
NSlog(@"%@", test);
[test release];
NSlog(@"%@", test);

我甚至添加了两个连续的释放操作,并在释放之后添加了一个测试= nil;,只是为了确保。


那个NSString可能被其他人保留了(检查retainCount)。 - kennytm
很抱歉,我试图添加一个新行,但不小心按下了回车键,导致问题过早发布。如果您足够友善,能否再次阅读我的问题?谢谢并对我的错误表示歉意。 - BBog
关闭ARC,我敢打赌这会导致你的崩溃。 - Michael Dautermann
不是ARC问题,因为他正在使用显式的“release”调用,在ARC下被禁止。 - Macmade
1
调用[[NSString alloc] init]很有可能会返回一个单例对象,这意味着你不能过度释放它。你应该尝试确保你有一个一次性的对象,例如使用[[NSMutableString alloc] init]。此外,在对象的创建/销毁过程中,值得在其周围加上自动释放池,以防有任何自动释放操作发生在幕后。 - Lily Ballard
3个回答

9

NSString 有时会表现出奇怪的行为。

在你的例子中,你分配了一个没有数据的 NSString
这就是它不崩溃的原因。

分配没有数据的 NSString 没有意义,因为 NSString 对象是不可变的。
在这种情况下,NSString 将返回一种类似于 singleton 实例,这意味着你无法释放它(当然你可以,但是它将没有任何效果)。
每次你以这种方式分配一个 NSString 对象时,都会返回相同的实例。

尝试使用以下代码:

NSString * test = [ [ NSString alloc ] initWithCString: "hello, world" encoding: NSUTF8StringEncoding ];

[ test release ];

NSLog( @"%@", test );

然后它会崩溃,如预期一样...

为了证明我之前所解释的,可以尝试以下操作:

NSString * test1 = [ [ NSString alloc ] init ];
NSString * test2 = [ [ NSString alloc ] init ];

NSLog( @"OK: %p %p", test1, test2 );

您可以看到打印的是相同的地址,这意味着只分配了一个对象(对于没有数据的NSString对象,“单例”对象)。 编辑 我看到了您的评论,尝试“给字符串赋值”。 这在使用NSString时不可能,因为它们是不可变的。
我猜您尝试过这个:
NSString * s = [ [ NSString alloc ] init ];

s = @"hello, world";

在这种情况下,你没有给出一个值你正在重新分配一个指针! 这意味着你的变量s(它是一个指针)将指向另一个字符串对象。 同时也意味着你将无法访问之前分配的字符串,因为你刚刚失去了指向它的指针。 如果你想要可更改的字符串对象,请看一下NSMutableString类。 但请记住,改变指针值并不等同于改变对象的值!

谢谢,我明天会在工作中尝试这个答案并接受它,如果它崩溃:))奇怪的是,我也尝试使用数据。我分配了字符串,给了它一个值,打印出来。然后释放它并再次打印。它打印了null,但应用程序没有崩溃。 - BBog
1
你所说的“赋值”是什么意思?正如我所说,NSString对象是不可变的。因此,我认为你只是将指针分配给另一个有效实例...在这种情况下,您没有更改对象的值,而是将指向原始对象的指针分配给了另一个对象。 - Macmade
我的意思是我使用了类似 test = @"abcd" 的东西。 - BBog
哇,现在我感觉真的很愚蠢... 在我的辩护中,我只有4个月的Objective-C编程经验。无论如何,今天我学到了新东西,谢谢!我真的很感激你非常详细地解释了一切。 - BBog
没问题,很高兴现在你清楚地理解了所有这些。愉快编码!:) - Macmade
显示剩余2条评论

2
如果您查看使用@"some_string"[[NSString alloc] init]分配的NSString对象的retainCount,您会发现它是-1。这是因为编译器在编译时已知道对象是什么,并且已经将其存储为字面值。因此,当您释放它时,实际上不会发生任何事情。它不是在运行时分配的对象 - 它只是始终存在于应用程序二进制文件中。
在Mac上,您可以通过执行以下操作来使崩溃发生:
#import <Foundation/Foundation.h>

int main(int argc, char** argv) {
    NSString *test = [[NSString alloc] initWithCString:argv[0] encoding:NSASCIIStringEncoding];
    NSLog(@"test = %p", test);
    [test release];
    NSLog(@"test = %p", test);
    [test release];
    NSLog(@"test = %p", test);
    return 0;
}

这个字符串现在不再是编译时已知的,因此它实际上将是一个已分配对象,并且具有正的保留计数等。

请注意,如果数字在编译时已知,则NSNumber(在Mac OS X Lion上)也会表现出这种行为。这更有趣,因为它是标记指针的一个示例。请参阅此博客文章进行讨论。下面是一些代码来展示它:

#import <Foundation/Foundation.h>

int main(int argc, char** argv) {
    NSNumber *test = [[NSNumber alloc] initWithInt:1];
    NSLog(@"test = %p", test);
    [test release];
    NSLog(@"test = %p", test);
    [test release];
    NSLog(@"test = %p", test);
    return 0;
}

请注意,程序并没有崩溃,它只是打印出了test = 0x183这个结果,一共打印了3次。

我也尝试在释放后使用test = nil;来确保,但它也没有以那种方式崩溃。我明天会在工作中尝试您的建议。谢谢您的帮助和解释!我非常感激。 - BBog

2

你的示例中发生了两件事情。

首先,在你释放字符串并打印NSLog之间没有其他操作会修改该字符串存储的内存,因此它偶然仍然完好无损。但是你不能依赖这一点。

其次,系统针对[[NSString alloc] init]有一个特殊情况。它返回一个预定义的__NSCFConstantString类型的对象,该对象永远不会被释放。如果你打印它的引用计数,你会看到它是2147483647(0x7fffffff)。这是一个魔法数字,表示任何尝试保留或释放该对象的操作都将被忽略。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接