为什么不崩溃?

Dav*_*ong 9 memory-management objective-c autorelease

我试图将一个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;
}
Run Code Online (Sandbox Code Playgroud)

期待这段代码崩溃.相反,它记录:

2011-01-18 14:41:06.311 EmptyFoundation[61419:a0f] static: static: 
Run Code Online (Sandbox Code Playgroud)

但是,如果我NSLog()改为:

NSLog(@"static: %s", [staticString UTF8String]);
Run Code Online (Sandbox Code Playgroud)

然后,它不会崩溃.

编辑更多信息:

排水后:

NSLog(@"static: %@", staticString);  //this logs "static: static: "
NSLog(@"static: %@", [staticString description]); //this crashes
Run Code Online (Sandbox Code Playgroud)

所以显然在字符串上调用一个方法就足以让它崩溃了.在这种情况下,为什么不直接记录字符串导致它崩溃?不NSLog()应该调用-description方法?

第二个"静态"来自何处?为什么这不会崩溃?


结果:

Kevin Ballard和Graham Lee都是正确的.格雷厄姆在意识到正确的NSLog()调用-description(因为我是错误的假设),和凯文几乎是绝对正确的,这是复制一个格式字符串和一个奇怪的堆栈相关的问题va_list各地.

  1. NSLogging并且NSString不会调用-description.格雷厄姆优雅地展示了这一点,如果你追踪进行日志记录的核心基金会来源,你会发现情况就是如此.源自内部的任何回溯都NSLog表明它调用NSLogv=> _CFLogvEx=> _CFStringCreateWithFormatAndArgumentsAux=> _CFStringAppendFormatAndArgumentsAux. _CFStringAppendFormatAndArgumentsAux()(第5365行)是所有魔法发生的地方.您可以看到它是手动查找所有%替换.如果替换的类型是a CFFormatObjectType,描述函数是非零的,并且替换尚未被另一种类型处理,它最终只会调用描述复制函数.由于我们已经证明描述没有被复制,因此可以合理地假设NSString早期处理(在这种情况下它可能会进行原始字节复制),这使我们相信......
  2. 凯文推测,这里有一个堆栈错误.不知何故,指向自动释放字符串的指针替换为另一个对象,这恰好是一个NSString.所以,它不会崩溃.奇怪的.但是,如果我们将静态变量的类型更改为其他内容(如a)NSArray,则会-description调用该方法,并且程序会按预期崩溃.

多么真实和完全奇怪.关于这个行为的根本原因,凯文是最正确的要点,并赞扬格雷厄姆纠正我的谬误.我希望我能接受两个答案......

Lil*_*ard 9

我对你所看到的最好的猜测是NSLog()复制格式字符串(可能是一个可变副本),然后解析参数.由于你已经dealloc'd staticString,所以恰好将格式字符串的副本放在同一个位置.这会导致您看到"static: static: "您描述的输出.当然,这种行为是未定义的 - 不能保证它总是会使用相同的内存位置.

另一方面,您NSLog(@"static: %s", [staticString UTF8String])正在staticString格式字符串复制发生之前访问,这意味着它正在访问垃圾内存.


小智 8

您对实例NSLog()调用的假设是错误的.我刚刚添加了这个类别:-descriptionNSString

@implementation NSString (GLDescription)

- (NSString *)description {
  NSLog(@"-description called on %@", self);
  return self;
}

@end
Run Code Online (Sandbox Code Playgroud)

它不会导致堆栈溢出,因为它不会被递归调用.不仅如此,如果我将该类别插入到您问题的代码中,我会发现此输出:

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: 
Run Code Online (Sandbox Code Playgroud)

因此,我们得出结论,NSLog()不叫-descriptionNSString在其ARGS遇到.当您错误地访问已释放的staticString变量时,为什么两次获取静态字符串可能是堆栈中数据的怪癖.