Objective c 为什么';这次坠机不是吗?
我正试图把一个bug缩小到一个最小的可重复的情况,并发现了一些奇怪的东西 考虑以下代码:Objective c 为什么';这次坠机不是吗?,objective-c,memory-management,autorelease,Objective C,Memory Management,Autorelease,我正试图把一个bug缩小到一个最小的可重复的情况,并发现了一些奇怪的东西 考虑以下代码: static NSString *staticString = nil; int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; if (staticString == nil) { staticString = [[NSA
static NSString *staticString = nil;
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
if (staticString == nil) {
staticString = [[NSArray arrayWithObjects:@"1", @"2", @"3", nil] componentsJoinedByString:@","];
}
[pool drain];
NSLog(@"static: %@", staticString);
return 0;
}
我预计这段代码会崩溃。相反,它记录:
2011-01-18 14:41:06.311 EmptyFoundation[61419:a0f] static: static:
但是,如果我将NSLog()
更改为:
NSLog(@"static: %s", [staticString UTF8String]);
然后它就崩溃了
编辑更多信息:
排干水池后:
NSLog(@"static: %@", staticString); //this logs "static: static: "
NSLog(@"static: %@", [staticString description]); //this crashes
显然,对字符串调用一个方法足以使其崩溃。在这种情况下,为什么记录字符串不会直接导致它崩溃?NSLog()
不应该调用-description
方法吗
第二个“静态:”来自哪里?为什么这不会崩溃
结果: 凯文·巴拉德和格雷厄姆·李都是正确的。格雷厄姆正确地认识到,
NSLog()
没有调用-description
(正如我错误地假设的那样),凯文几乎完全正确地认识到,复制格式字符串和va_列表时,这是一个奇怪的堆栈相关问题
NSLogging
和NSString
不会调用-description
。格雷厄姆优雅地展示了这一点,如果你通过核心基础来进行日志记录,你会发现这是事实。任何源自NSLog
内部的回溯都显示它调用NSLogv
=>\u CFLogvEx
=>\u cfstringcreatewithformat和argumentsaux
=>>\u cfstringappendformat和argumentsaux
。(第5365行)是所有魔法的所在。您可以看到它正在手动查找所有的%
替换。仅当替换类型为CFFormatObjectType
、描述函数为非nil且替换尚未由其他类型处理时,才会调用description copy函数。由于我们已经证明描述没有被复制,因此可以合理地假设NSString
更早地得到处理(在这种情况下,它可能会进行原始字节复制),这让我们相信
正如凯文推测的,这里发生了堆栈错误。不知何故,指向自动释放字符串的指针被替换为另一个对象,该对象恰好是NSString
。所以,它不会崩溃。奇怪的但是,如果我们将静态变量的类型更改为其他类型,如NSArray
,则会调用-description
方法,并且程序会按预期崩溃
真是太奇怪了。凯文在行为的根本原因上是最正确的,格雷厄姆纠正了我的错误想法,这是值得称赞的。我希望我能接受两个答案…访问解除分配的内存不一定会导致崩溃。该行为未定义。你期望太高了 这可能与@“static:”存储在与staticString相同的内存位置有关。staticString将被解除分配,它将@“static:%@”存储在回收的mem位置,因此staticString指针位于“static:%@”上,因此它最终为static:static:。我对您看到的最好猜测是,NSLog()复制格式字符串(可能是可变副本),然后解析参数。由于您已解除锁定了staticString
,因此格式字符串的副本恰好被放置在同一位置。这会导致您看到所描述的“static:static:”
输出。当然,这个行为是未定义的——不能保证它总是使用相同的内存位置
另一方面,您的NSLog(@“static:%s”,[staticString UTF8String])
在格式字符串复制发生之前正在访问staticString
,这意味着它正在访问垃圾内存。您认为NSLog()
调用实例上的-description
是错误的。我刚刚添加了以下类别:
@implementation NSString (GLDescription)
- (NSString *)description {
NSLog(@"-description called on %@", self);
return self;
}
@end
它不会导致堆栈溢出,因为它不会被递归调用。不仅如此,如果我将该类别插入到您问题中的代码中,我会发现以下输出:
2011-01-18 23:04:11.653 LogString[3769:a0f] -description called on 1
2011-01-18 23:04:11.656 LogString[3769:a0f] -description called on 2
2011-01-18 23:04:11.657 LogString[3769:a0f] -description called on 3
2011-01-18 23:04:11.658 LogString[3769:a0f] static: static:
因此,我们得出结论,NSLog()
不会对在其参数中遇到的NSString
调用-description
。当您错误地访问发布的staticString
变量时,为什么两次获取静态字符串可能是堆栈上数据的一个怪癖。这是“在free()之后使用”(
)。发生的是“未定义的行为”。您的示例与以下示例无异:
char *stringPtr = NULL;
stringPtr = malloc(1024); // Example code, assumes this returns non-NULL.
strcpy(stringPtr, "Zippers!");
free(stringPtr);
printf("Pants: %s\n", stringPtr);
在printf
行发生了什么?谁知道呢。从<代码>裤子:拉链代码>到<代码>裤子:(…垃圾…)堆芯转储
所有特定于Objective-C的东西实际上都是不相关的——事实上,您使用的内存指针不再有效,这是唯一重要的事情。你最好向墙上扔飞镖,而不是试图解释“为什么”它没有崩溃并打印static:static
。出于性能原因,大多数malloc
实现在必要时才开始“收获”free()
'd分配。这可能就是为什么你的例子没有像你期望的那样崩溃
如果您真的想看到此特定程序崩溃,可以执行以下操作之一:
- 将环境变量
CFZombieLevel
设置为17
(scribble+不释放)
- 将环境变量
NSZombieEnabled
设置为YES
- 将环境变量
DYLD\u INSERT\u LIBRARIES
设置为/usr/lib/libgmalloc.dylib
(请参见man libgmalloc
)
如果在排空池之前放置日志,则它会按预期记录。当我排空时,我希望NSString