Delphi中一个超出范围的对象会发生什么?

5

当一个对象在函数内被创建并且函数执行完成后,如果没有显式地销毁该对象,它会发生什么?

所有变量都需要在其作用域结束时被销毁吗?还是它们在作用域结束时会被自动处理?

例如,custom_function 被调用后,locallist 会发生什么?

function TForm1.custom_function(string: test_string): boolean;
var locallist: TStringList;
begin
  locallist := TStringList.Create;
  // do a bunch of stuff here, but don't destroy locallist
  return true;
end;
5个回答

17
你会得到一个内存泄漏。
正确的模式是:
myObject := TObject.Create;
try
  //do stuff
finally
  myObject.Free;
end;

如果你需要在后面测试对象是否已经被释放,那么可以使用FreeAndNil(myObject)。它会将变量设置为nil,这样你以后就可以进行测试。


13

正如其他帖子所指出的,这些对象需要明确地释放。通常通过使用try..finally块来手动完成,就像Ray所演示的那样。但是有一些例外,你需要注意。

组件(TComponent的派生类)在其构造函数中传递Owner参数。如果owner不是nil,则owner组件将接管新组件并在其被释放时释放它。这就是为什么你不必清理自己的窗体;它们连接到应用程序对象,后者知道如何在程序完成时释放自身。但是,如果你在运行时创建一个组件,则需要为其分配一个owner或将nil传递给构造函数,然后自己释放它。不要混合使用具有owner的组件进行释放。在某些情况下,这可能会导致双重释放条件。

实现引用计数的接口对象(主要是TInterfacedObject的派生类)如果你特别将它们称为接口(而不是对象),则会通过引用计数机制来释放它们。当最后一个接口引用从它们移除时,它们会自动释放。如果你已经将其分配给接口引用,请勿手动释放TInterfacedObject。这将引发异常。此外,请注意,并非所有具有接口的对象都实现了引用计数。主要只是从TInterfacedObject派生的那些对象。

创建一个对象、使用try..finally块,然后释放它并不总是切实可行的。有时候这对于你正在做的事情并没有什么作用,特别是如果你正在将该对象分配给某种列表(并同时创建大量对象)。在这种情况下,最好使用具有OwnsObjects属性设置为true的TObjectList(或更好的是,如果你有D2009,则为TObjectList)。这将导致列表成为其中对象的所有者,并在其被释放时释放它们,就像组件一样。再次强调,如果对象由对象列表拥有,请勿手动释放该对象。

动态数组(包括字符串)由编译器通过引用计数系统进行管理,大多数其他类型的变量都分配在堆栈上。除非你在玩指针,否则你永远不必担心手动释放除对象以外的任何东西。

这可能听起来很复杂,但你很快就会习惯它。只需要记住每个对象都由三个东西中的一个拥有:另一个对象、接口引用计数系统或者你的代码,当它们不再需要时,所有者应该释放其所有对象。任何东西都不应该尝试释放被其他东西拥有的东西。(不可偷窃)遵循这些准则,你将获得良好的内存管理。你还可以在DPR的主程序中设置“ReportMemoryLeaksOnShutdown := true”以获得更多帮助。


1
请勿通过释放拥有者的组件来混合两者。这将导致双重释放条件。我们能否停止重复这个问题?这是错误的。释放具有所有者的组件将从所有者维护的所拥有组件列表中移除它。这可能不太优化,或令人困惑... - mghie
基本上,这就像在迭代数组/列表的for循环中修改它。通常是安全的,但如果你不小心的话,可能会很危险,最好不要这样做。 - Mason Wheeler
@Mason:你的理由并没有说服我。按照这个逻辑,我们应该完全不鼓励使用指针,因为总有可能在指向的对象被销毁后对它们进行解引用操作。而你的“编辑”实际上是在说:“不要使用指针,因为那样会导致问题。” - mghie
那么到底是哪一个 - “不要释放拥有者的组件”,还是“这两个都没问题”?你不能两全其美,因为这个窗体有一个拥有者。 - mghie
1
caFree只是改变了TForm.Close的正常行为,它是安全的。TForm.Release是一个释放操作,允许窗体在进入销毁过程之前完成消息队列和其他处理。两者都是根据具体情况量身定制的。Mason的方法更像是一般规则,而我已经这样做了(如果我创建的窗体在应用程序之前被销毁,它就有一个nil owner)。 - Fabricio Araujo
显示剩余5条评论

5

这是泄漏内存的表现。

通常,您应该这样包装这些分配:

locallist := TStringList.Create;
try
     // work with locallist here
  finally
     locallist.Free;
  end; 

在Delphi中,唯一一种在超出范围时自杀的引用是接口引用。


和字符串引用以及接口引用 - Barry Kelly
创建(create)始终在尝试(try)之前。 - Ray
如果您使用此方法,每个对象都会收到警告,因为创建可能会失败,然后尝试释放该对象。 - Ray
如果您首先将localList := nil设置为零,那么就不会收到警告。如果您一次创建多个对象,则这是首选模式。 - Jim McKeeth
打字太快,没有重新阅读,我自食恶果。就像我每天都要使用这个模式一样<sigh>。 我编辑了我的回复,将创建移出try,放在它应该的地方。 - Jim

3
这将导致内存泄漏,只有在应用程序结束时才会被销毁。要检测这些问题,您可以使用像FastMM这样的内存管理器。
请注意,您可以使用Destroy或Free方法销毁对象。如果对象为nil,则前者会报错,而后者不会——它等同于
if Assigned(Object) then Object.Destroy;

那么,FastMM工具是编译进应用程序中的东西,还是Delphi编辑器/编译器的某种插件? - Dave
它被编译到应用程序中。您将其添加到使用条款中,它将被添加到您的项目中。它有两个效果:1. 加速Delphi内存例程,2. 检测内存泄漏。当在编译器中运行项目时,内存泄漏会报告堆栈跟踪。 - schnaader
我认为FastMM已经被纳入了后来的Delphi版本中(至少是加速部分)。但我可能错了。 - Ray
大多数FastMM已经整合到最近的几个Delphi版本中,但是在http://sourceforge.net/projects/fastmm/上有一个更具功能丰富性的版本,提供额外的选项来帮助调试。 - Mason Wheeler
如果已分配对象,则应该调用 Object.Destroy;否则会递归! - Gerry Coll
不需要检查对象是否被分配。Free方法会为您执行此操作。 - dan-gph

3

内存没有被回收,抱歉。您需要使用locallist.Free显式释放此对象;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接