Почему не происходит сбой?
Я пытаюсь сузить ошибку до минимально воспроизводимого случая и обнаружил что-то странное.
Рассмотрим этот код:
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_list
вокруг.
NSLogging
а такжеNSString
не вызывает-description
, Грэм изящно показал это, и если вы проследите через источники Core Foundation, которые ведут журналирование, вы увидите, что это так. Любой след, происходящий внутриNSLog
показывает, что это вызываетNSLogv
=>_CFLogvEx
=>_CFStringCreateWithFormatAndArgumentsAux
=>_CFStringAppendFormatAndArgumentsAux
,_CFStringAppendFormatAndArgumentsAux()
(строка 5365), где происходит вся магия. Вы можете видеть, что это происходит вручную, чтобы найти все%
замены. В конечном итоге вызывается функция копирования описания, только если тип заменыCFFormatObjectType
функция описания не равна нулю, а подстановка еще не была обработана другим типом. Поскольку мы показали, что описание не копируется, разумно предположить, чтоNSString
обрабатывается раньше (в этом случае он, вероятно, будет делать необработанную байтовую копию), что заставляет нас верить...- Здесь происходит ошибка стека, как предполагает Кевин. Каким-то образом указатель, который указывал на автоматически освобожденную строку, заменяется другим объектом, который оказывается
NSString
, Таким образом, это не терпит крах. Weird. Однако, если мы изменим тип статической переменной на что-то другое, напримерNSArray
тогда-description
метод вызывается, и программа завершается с ошибкой, как и ожидалось.
Как по-настоящему и совершенно странно. Баллы идут к Кевину за то, что он наиболее правдив в отношении основной причины поведения, и благодарность Грэму за исправление моего ошибочного мышления. Я хотел бы принять два ответа...
5 ответов
Мое лучшее предположение для того, что вы видите, заключается в том, что NSLog() копирует строку формата (вероятно, в виде изменяемой копии), а затем анализирует аргументы. Так как вы сделали staticString
Просто так получилось, что копия строки формата помещается в то же место. Это заставляет вас видеть "static: static: "
вывод, который вы описали. Конечно, это поведение не определено - нет гарантии, что он всегда будет использовать одну и ту же область памяти для этого.
С другой стороны, ваш NSLog(@"static: %s", [staticString UTF8String])
получает доступ staticString
до того, как произойдет копирование строки формата, это означает, что он обращается к мусорной памяти.
Ваше предположение, что 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
переменная.
Доступ к освобожденной памяти не обязательно вызывает сбой. Поведение не определено. Вы ожидаете слишком многого!
Возможно, это как-то связано с тем, что @ "static:" хранится в той же ячейке памяти, что и staticString. staticString будет освобожден, и он сохранит @ "static:% @" в том месте, где был восстановлен mem, поэтому указатель staticString находится на "static:% @", поэтому он заканчивается static: static:.
Это случай "Использовать после free()
Msgstr"То, что происходит, это" неопределенное поведение ". Ваш пример действительно ничем не отличается от:
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
).