我正在尝试将错误范围缩小到最小可重复的情况并发现奇怪的事情。
考虑以下代码:
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
方法吗?
第二个“静态”来自哪里?为什么这不会崩溃?
结果:
Kevin Ballard和Graham Lee都是正确的。 Graham正确地认识到NSLog()
不调用-description
(正如我错误地假设的那样),并且Kevin几乎肯定是正确的,这是与复制相关的奇怪堆栈问题格式字符串和va_list
。
NSLogging
和NSString
未调用-description
。格雷厄姆优雅地展示了这一点,如果你追踪进行日志记录的核心基金会来源,你会发现情况就是如此。源自NSLog
内的任何回溯都会显示它调用NSLogv
=> _CFLogvEx
=> _CFStringCreateWithFormatAndArgumentsAux
=> _CFStringAppendFormatAndArgumentsAux
。 _CFStringAppendFormatAndArgumentsAux()
(第5365行)是所有魔法发生的地方。您可以看到它是手动查找所有%
替换。如果替换的类型是CFFormatObjectType
,描述函数是非零的,并且替换尚未被另一种类型处理,它最终只会调用描述复制函数。由于我们已经证明描述没有被复制,因此可以合理地假设NSString
得到了更早的处理(在这种情况下,它可能会进行原始字节复制),这使我们相信.. NSString
。所以,它不会崩溃。奇怪的。但是,如果我们将静态变量的类型更改为其他内容(如NSArray
),则会调用-description
方法,并且程序会按预期崩溃。多么真实和完全奇怪。关于行为的根本原因,要点凯文是最正确的,并且要求格雷厄姆纠正我的谬误。我希望我能接受两个答案......
答案 0 :(得分:9)
我对你所看到的最好的猜测是NSLog()复制格式字符串(可能是一个可变副本),然后解析参数。由于你已经解除了staticString
,所以恰好将格式字符串的副本放在同一个位置。这导致您看到您描述的"static: static: "
输出。当然,这种行为是未定义的 - 不能保证它总是会使用相同的内存位置。
另一方面,在格式化字符串复制发生之前,您的NSLog(@"static: %s", [staticString UTF8String])
正在访问staticString
,这意味着它正在访问垃圾内存。
答案 1 :(得分:8)
您假设NSLog()
在-description
实例上调用NSString
是错误的。我刚刚添加了这个类别:
@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()
并未在-description
上调用NSString
。当您错误地访问已发布的staticString
变量时,为什么两次获取静态字符串可能是堆栈中数据的怪癖。
答案 2 :(得分:1)
访问已分配的内存并不一定会导致崩溃。行为是未定义。你期待太多了!
答案 3 :(得分:1)
可能与@“static:”有关,它与staticString存储在同一个内存位置。 staticString将被释放,它将@“static:%@”存储在该循环的mem位置中,因此staticString指针位于“static:%@”上,因此它最终为static:static:。
答案 4 :(得分:1)
这是“在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
行会发生什么?谁知道。从Pants: Zippers!
到Pants: (...garbage...) Core Dump
的任何内容。
所有Objective-C特定的东西实际上都是无关紧要的 - 事实上你正在使用一个不再有效的内存指针是唯一重要的事情。你最好不要在墙壁上投掷飞镖而不是试图解释“为什么”它不会崩溃和打印static: static
。出于性能原因,大多数malloc
实现都不会在需要时“收获”free()
分配。恕我直言,这可能就是为什么你的例子没有像你期望的那样崩溃的原因。
如果您确实希望看到 此 特定程序崩溃,您可以执行以下操作之一:
CFZombieLevel
设置为17
(涂鸦+不要免费)。NSZombieEnabled
设置为YES
。DYLD_INSERT_LIBRARIES
设置为/usr/lib/libgmalloc.dylib
(请参阅man libgmalloc
)。