繁体   English   中英

目标C中的细微内存管理问题

[英]Subtle memory management issue in Objective C

我最近(经过数小时的调试)最近在Objective C iPad应用程序中发现了段错误。 归根结底,我有一个对象TOP,它拥有MIDDLE,它拥有BOTTOM。 MIDDLE和BOTTOM保留计数为1。MIDDLE将BOTTOM传递给TOP中的方法,该方法最终释放MIDDLE,从而导致BOTTOM被释放和解除分配。 当TOP中的相同方法继续与BOTTOM一起使用时,它出现故障。 (请注意,为了简化起见,我省略了多层间接描述,但这使调试变得很繁琐。)

发生的事情有名字吗? 我是否可以遵循某种模式来防止将来发生这种情况? 为什么运行时为什么不通过本质上用[self retain][self release]包装方法(或以相同方式保留参数,或两者都保留)来在调用堆栈上保留对象?

编辑:

需要明确的是,当TOP释放MIDDLE时,它将指针设置为nil。 永远不会通过无效的指针访问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

对于一般问题,除了它的工作方式之外没有一个很好的答案。 非垃圾收集的Objective-C是一种显式的内存管理语言。 这意味着您需要照顾对象所有权的语义。 有一些约定可以帮助您做到这一点,但最终您必须注意谁拥有什么。

现在,您可以辩称,仅在调用堆栈上存在是一种事实上的所有权,并且肯定可以以这种方式设计语言。 但是,在实践中,这将带来很多多余的保留和释放,这在所有精心设计的代码中都是不必要的,并且仅在极少数情况下提供了虚假的安全缓冲区。 这也可能会使许多真正的内存管理错误更难找到。

您当然有权不同意这种分析,但是您和我对此都没有发言权。

在您描述的特定情况下,在我看来,问题是当从MIDDLE中传递BOTTOM时,您没有正确定义所有权语义。 有两种可能性:

  1. MIDDLE返回弱引用,该引用随后可能消失。 在这种情况下,必须先retain TOP,然后再进行可能会更改BOTTOM状态的任何操作-并且在MIDDLE上调用release一定属于该类别。
  2. MIDDLE赋予调用方临时所有权,在自动释放池耗尽之前,引用将保持良好状态: return [[BOTTOM retain] autorelease]; 在这种情况下,以后release MIDDLE是安全的。

正如Objective-C所言,这两种方法都是有效的方法,并且两者都将起作用。 您只需要明确决定使用的对象并采取相应的措施即可。 您的错误源于第一次执行,但期望它的行为像第二次一样。

运行时无法合理地为您保留和释放每个参数的原因有很多。

  1. 人们已经抱怨Objective-C的效率低下。 除了该方法的实际代码之外,对于每个方法调用都强制使用arity * 2消息会很麻烦。

  2. 不能假定任意对象响应retainrelease 这对于符合NSObject类是唯一的。

  3. 争论可以而且经常是反弹。 为此,运行时必须跟踪参数的初始值。

  4. 有一些原因不希望这种行为,这似乎至少与想要这种行为的原因一样有效(本质上,在您不想遵循内存管理合同的麻烦的情况下,它可以简化设计)。 可能存在一种方法,有意释放一个参数并在其位置分配一个新参数(沿init方法行),对于大对象,这可能导致更大的内存占用。 retainrelease的主要平台iPhone上,这尤其麻烦。

在TOP中,您可能有指向MIDDLE的指针。 释放指向MIDDLE的指针时,是否将指针设置为NULL,以便知道内存可能无效?

我称其为倒置的Münchhausen( http://en.wikipedia.org/wiki/Baron_M%C3%BCnchhausen )。

并且:为什么您的TOP不保留该对象,它却愿意拥有该对象(至少在该对象正在使用时以及在使用它时)? 因为它是由另一个对象拥有,由TOP拥有? 展开此图像:应用程序以某种方式拥有每个对象。 那么为什么要所有这些保留发行游戏呢? 应该数一数! 或不?

问候

“为什么运行时不保留方法参数”? 没人告诉我你的名字叫Yossaran: http : //developer.apple.com/mac/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-1044135

问候

您是否真的尝试过在TOP方法开始时(在发布MIDDLE之前)保留BOTTOM?

如果是这样,并且这不起作用,请检查您是要释放MIDDLE和BOTTOM,还是要强迫其中之一或两者立即取消分配(通过直接调用dealloc而不是release)。 当您只是在MIDDLE的dealloc方法中释放BOTTOM时,如果还有另一个对象保留它,则系统不应该对其进行释放。

我希望我不会错过这一点-如果是这样,也许您应该通过发布一些代码来阐明您的问题,包括从TOP到BOTTOM的发行链。

虽然我仍然会尝试解决此问题,但为什么不将MIDDLE的发布推迟到TOP不再需要BOTTOM为止?

我是否可以遵循某种模式来防止将来发生这种情况?

是。 您的TOP对象需要声明BOTTOM对象的所有权。 您拥有通过调用“ alloc”,“ new”或“ copy”获得的任何对象。 我们可能会假设您通过调用这三个方法之一未获得对BOTTOM对象的引用,这意味着如果您希望BOTTOM坚持下去,则需要对其进行“保留”。

很遗憾,您不得不花时间来跟踪错误,但是从听起来,您的对象的行为完全符合约定。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM