Тонкая проблема управления памятью в Задаче C - PullRequest
1 голос
/ 19 августа 2010

Я недавно (после нескольких часов отладки) обнаружил ошибку в приложении Objective C iPad.Чтобы свести это к минимуму, у меня был объект TOP, который владел MIDDLE, который владел ДНО.MIDDLE и BOTTOM сохранили количество единиц 1. MIDDLE передал BOTTOM методу в TOP, который в итоге выпустил MIDDLE, в результате чего BOTTOM был освобожден и освобожден.Когда тот же метод в TOP продолжал работать с BOTTOM, он вышел из строя.(Обратите внимание, что есть несколько уровней косвенности, которые я упустил из описания, чтобы упростить его, но который сделал отладку рутинным.)

Есть ли название для того, что произошло?Есть ли способ, которым я могу следовать, чтобы предотвратить это в будущем?Почему среда выполнения не сохраняет объекты в стеке вызовов, по существу оборачивая методы с [self retain] и [self release] (или сохраняя аргументы одинаково или оба)?

Редактировать:

Для ясности, когда TOP выпускает MIDDLE, он устанавливает указатель на ноль.К СРЕДНЕМУ никогда нельзя получить доступ через недействительный указатель.

Редактировать 2: Я должен был опубликовать фактический код для начала.По сути это то, что у меня есть:

// also known as TOP
@interface MyAppDelegate : NSObject <UIApplicationDelegate> {
  UIViewController* controller;
}
@end

@implementation MyAppDelegate
- (void)displayDoc:(Document*)doc {
  DocController* c = [[DocController alloc] initWithNibName:@"DocController" bundle:nil doc:doc];
  [controller release];
  // controller was previously an instance of HomeController
  controller = c;
}

- (void)displayBookmark:(Bookmark*)bookmark {
  [self displayDoc:bookmark.document];
  [controller setPage:bookmark.page];
}

- (void)dealloc {
  [controller release];
  [super dealloc]
}
@end


// also known as MIDDLE
@interface HomeController : UIViewController {
}
@end

@implementation HomeController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  Bookmark* b = ...; // pull out of existing data structure, not created here
  MyAppDelegate* app = ...;
  [app displayBookmark:b];
}
@end


// also known as BOTTOM
@interface Bookmark : NSObject {
}
@property NSString* name;
// etc.
@end

Ответы [ 7 ]

3 голосов
/ 23 августа 2010

На общий вопрос не очень хороший ответ, кроме , как он работает .Objective-C без сборщика мусора - это язык с явным управлением памятью.Это означает, что вы должны заботиться о семантике владения объектом.Есть соглашения, которые помогут вам сделать это, но в конечном итоге вы должны обратить внимание на то, кто чем владеет.

Теперь вы можете утверждать, что простое присутствие в стеке вызовов является своего рода de facto * 1006.* владение, и, конечно, можно разработать язык таким образом.Тем не менее, на практике это повлечет за собой много лишнего хранения и освобождения, которое не нужно во всем хорошо спроектированном коде и обеспечит надежный буфер безопасности только в очень немногих крайних случаях.Это, вероятно, также затруднило бы поиск множества подлинных ошибок управления памятью.

Вы, конечно, вправе не согласиться с этим анализом, но ни вы, ни я ничего не можем сказать по этому поводу.

В конкретном случае, который вы описываете, мне кажется, проблема в том, что вы не правильно определили семантику владения при передаче BOTTOM из MIDDLE.Есть две возможности:

  1. MIDDLE возвращает слабую ссылку, которая впоследствии может исчезнуть.В этом случае до TOP 101 *, прежде чем делать что-либо, что может изменить состояние BOTTOM - и вызов release на MIDDLE, безусловно, попадает в эту категорию.
  2. MIDDLE присваивает вызывающему временное владениес эталоном, который, как ожидается, будет оставаться хорошим до тех пор, пока пул авто-выпуска не истощится: return [[BOTTOM retain] autorelease]; В этом случае безопасно release СРЕДНИЙ впоследствии.

Оба из этих подходов являются действительными в качестве Objective-Cстоит, и оба будут работать.Вам просто нужно четко определить, что вы используете, и действовать соответственно.Ваша ошибка возникла в результате выполнения первого, но ожидалось, что она будет вести себя как вторая.

2 голосов
/ 23 августа 2010

Существует ряд причин, по которым среда выполнения не может разумно сохранить и освободить каждый аргумент для вас.

  1. Люди уже жалуются на неэффективность Objective-C. Принудительное использование сообщений arity * 2 для каждого вызова метода в дополнение к фактическому коду метода будет чокнутым.

  2. Предполагается, что произвольный объект не отвечает на retain и release. Это уникально для классов, соответствующих NSObject.

  3. Аргументы могут быть и часто являются отскоком. Для этого среда выполнения должна отслеживать начальные значения аргументов.

  4. Есть причины не хотеть такого поведения, которые кажутся, по крайней мере, такими же обоснованными, как и причины этого желания (по сути, это может упростить дизайн в тех случаях, когда вы не хотите перейти к трудностям следования контракту на управление памятью). Может существовать метод, который преднамеренно освобождает аргумент и размещает новый вместо него (в соответствии с методом init), а в случае больших объектов это может привести к гораздо большему использованию памяти. Это было бы особенно неприятно на iPhone, который является основной платформой для retain и release.

0 голосов
/ 23 августа 2010

Есть ли образец, которому я могу следовать, чтобы предотвратить его в будущем?

Да. Ваш объект TOP должен претендовать на владение объектом BOTTOM. Вы владеете любым объектом, который получаете, вызывая «alloc», «new» или «copy». Вероятно, мы можем предположить, что вы не получаете ссылку на BOTTOM-объект, вызывая один из этих трех методов, что означает, что если вы хотите, чтобы BOTTOM оставался без присмотра, вам нужно вызвать 'retain' для него.

Отстойно, что вам пришлось потратить время на отслеживание вашей ошибки, но, судя по ее звукам, ваши объекты вели себя именно так, как того требуют соглашения.

0 голосов
/ 23 августа 2010

Вы действительно пытались сохранить BOTTOM прямо в начале метода TOP (перед выпуском MIDDLE)?

Если это так, и если это не работает, проверьте, просто ли вы отпускаете MIDDLE и BOTTOM или вы заставляете одно или оба сразу освободить (вызывая dealloc напрямую вместо release). Пока вы просто выпускаете BOTTOM в методе dealloc MIDDLE, система не должна удалять его, если есть еще один объект, сохраняющий его.

Надеюсь, я не упустил момент - если да, возможно, вам следует прояснить вашу проблему, разместив некоторый код, включая цепочку релизов от TOP до BOTTOM.

Хотя я все еще пытаюсь решить эту проблему, почему бы вам не отложить выпуск MIDDLE до тех пор, пока TOP больше не понадобится НИЖЕ?

0 голосов
/ 21 августа 2010

«почему среда выполнения не сохраняет аргументы метода»?Никто не сказал мне, что тебя зовут Йоссаран: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-1044135

Привет

0 голосов
/ 20 августа 2010

Я бы назвал это перевернутым Мюнхгаузеном (http://en.wikipedia.org/wiki/Baron_M%C3%BCnchhausen).

И: почему ваш ТОП не удерживает объект, он готов владеть (по крайней мере, на время, когда он работает нади вместе с ним)? Потому что он принадлежит другому объекту, принадлежащему TOP? Разверните это изображение: каждый объект каким-то образом принадлежит приложению. Так почему же нужно делать все эти игры с сохранением-релизом?или нет?

Привет

0 голосов
/ 19 августа 2010

В TOP у вас, вероятно, есть указатель на MIDDLE.Когда вы отпускаете указатель на MIDDLE, вы устанавливаете указатель на NULL, чтобы знать, что память может быть недействительной?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...