我正在尝试将一个bug缩小到最小可重现的程度,并发现了一些奇怪的事情。
请看这段代码:
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
方法吗?第二个“static:”从哪里来?为什么这不会导致崩溃?
结果:
Kevin Ballard和Graham Lee都是正确的。 Graham之所以正确,是因为他意识到
NSLog()
没有调用-description
(正如我错误地假设的那样),而Kevin几乎肯定是正确的,这是一个与复制格式字符串和va_list
相关的奇怪堆栈问题。1.
NSLogging
和NSString
不会调用-description
。 Graham很优雅地表现出了这一点,如果你跟踪执行日志记录的核心基础源代码,你会发现这是事实。任何起源于NSLog
内部的回溯都表明它会调用NSLogv
=> _CFLogvEx
=> _CFStringCreateWithFormatAndArgumentsAux
=> _CFStringAppendFormatAndArgumentsAux
。_CFStringAppendFormatAndArgumentsAux()
(第5365行)是发生所有魔法的地方。你可以看到它手动查找所有的%
替换。只有当替换类型为CFFormatObjectType
,描述函数非空,并且替换还没有被其他类型处理时,它才会最终调用描述副本函数。由于我们已经证明了描述未被复制,因此可以合理地假设一个NSString
在此之前就被处理了(在这种情况下,它可能会执行原始字节复制),从而导致……2. 正如Kevin所推测的那样,这里发生了堆栈错误。某种方式,最初指向自动释放字符串的指针被替换为不同的对象,这个对象恰好是一个
NSString
。因此,它不会崩溃。奇怪的是,如果我们将静态变量的类型更改为其他类型,例如NSArray
,那么-description
方法就会被调用,程序就会像预期的那样崩溃。多么真正和完全的奇怪啊。 Kevin因为对行为的根本原因最正确而得分,而Graham则因纠正我的谬误思维而受到赞扬。 我希望我能接受两个答案……
NSLog([NSString stringWithFormat:@"static: %@", staticString])
确实会导致崩溃,因此这实际上是由于NSLog
处理%@
时的不同行为所引起的。 - Joost_CFStringAppendFormatAndArgumentsAux
的分析是不正确的。对于每个%@
,_CFStringAppendFormatAndArgumentsAux
将尝试1)通过参数传递的copyDesc
函数;2)__CFCopyFormattingDescription
,这对于ObjC对象会尝试_copyFormattingDescription:
;最后3)CFCopyDescription
,这对于ObjC对象会尝试_copyDescription
,反汇编显示NSObject
默认调用-description
。因此,99%的情况下,%@
将导致调用-description
。 - johneNSString
对象时,并没有调用-description
方法,这意味着某些地方发生了短路。我曾认为它会被调用,而这种假设导致我期望出现崩溃。 - Dave DeLong-description
肯定会被调用,只是被NSLog()
“自动地”禁止了,因为(我猜测,可能与多线程锁有关)NSLog()
会阻塞(即在当前处理NSLog()
时立即返回而不执行任何工作)对NSLog()
的其他调用。 - johne