为什么不崩溃?

时间:2011-01-18 22:42:45

标签: objective-c memory-management autorelease

我正在尝试将错误范围缩小到最小可重复的情况并发现奇怪的事情。

考虑以下代码:

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

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

5 个答案:

答案 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)。