Delphi app中的内存泄漏。如何正确处理对象和字符串?

时间:2014-04-01 15:48:28

标签: delphi memory-leaks garbage resource-disposal

我的问题是调试内存泄漏,这似乎是一场噩梦。
在我的应用程序中,有一个派生自TObject的简单类。该类的所有对象都存储在从TObjectList

派生的类的集合/列表中
type
  TOffer = class(TObject)
    Item: string;
    Price: string;
    Id: string;
  end;

  TOffers = class(TObjectList<TOffer>)
  protected
    procedure SetOffer(I: Integer; AOffer: TOffer);
    function GetOffer(I: Integer): TOffer;
  public
    property Offers[I: Integer]: TOffer read GetOffer write SetOffer
  end;

使用场景:
抓取工具下载优惠,解析优惠并保存到对象集合。这种方法似乎很方便,因为我可以稍后引用这些对象(填充网格/列表,将它们写入文件等)。

问题是妥善处理对象以避免内存泄漏。该应用程序在启动时分配~4Mb内存,但在处理后~12k提供它吞噬32Mb。在过程完成后由未正确处理的对象/变量引起的泄漏。

ReportMemoryLeaksOnShutdown显示了可怕的数字,但关键是 - 我不知道在哪里看,以及如何正确调试该死的东西。

另一个例子是变量var MyString: string,它也需要妥善处理!!这对我来说很有见地:)我认为每个过程/函数都会自动管理超出范围的变量的垃圾收集。

优惠列表由函数创建:

function GetOffersList: TOffers;
begin
  Result := TOffers.Create;
  while not rs.EOF do
  begin
    Offer := TOffer.Create;
    try
       // here come collected offer attributes as variables of type string:
        Order.Item := CollectedOfferItem;
        Order.Price := CollectedOfferPrice;
        Order.Id := CollectedOfferId;
        Result.Add(Offer);
    finally
        Offer := nil;
    end;
  end;
end;

然后我直接将这些优惠作为一个集合来解决。关键是我希望这个应用程序全天候运行,因此必须正确处理资源。

  • 如何妥善处理上述类型的对象?
  • 我应该考虑管理对象/对象列表的其他技术吗?
  • 如何正确处理string类型的变量?
  • 请告诉您有关打击Delphi中内存泄漏的良好解读?

谢谢。

2 个答案:

答案 0 :(得分:8)

默认情况下,当您创建对象时,您将成为其所有者。只要您是所有者,您就有责任将其释放。以下是一些常见模式:

<强> 1。本地变量

对于在方法中创建且仅在本地引用的对象,您使用try / finally模式:

Obj := TMyClass.Create;
try
  ... use Obj
finally
  Obj.Free;
end;

<强> 2。对象由另一个对象拥有

通常在构造函数中创建并在析构函数中销毁。这里有一个拥有对象的成员字段,用于保存对拥有对象的引用。您需要做的就是在拥有类析构函数中的所有拥有对象上调用Free

第3。拥有的TComponent

如果使用TComponent创建Owner或派生类,则该所有者会销毁该组件。你不需要。

<强> 4。 TObjectList或类似的OwnsObjects设置为True

您在问题中显示此模式。您创建了TObjectList<T>,默认情况下OwnsObjectsTrue。这意味着当您向容器添加成员时,容器将承担所有权。从那时起,集装箱承担了销毁其成员的责任,而你不必这样做。但是,有人仍然需要销毁容器。

<强> 5。引用计数的接口对象

常见示例是从TInterfacedObject派生的对象。接口引用计数管理生命周期。你不需要销毁这个物体。

<强> 6。创建并返回新实例的函数

这是一个更棘手的结束。值得庆幸的是,它是一种相当罕见的模式。这个想法是该函数将一个新实例化和初始化的对象返回给调用者,然后调用者承担所有权。但是当函数仍在执行时,它是所有者并且必须防止异常。通常代码如下:

function CreateNewObject(...): TMyClass;
begin
  Result := TMyClass.Create;
  try
    Result.Initialize(...);
  except
    Result.Free;
    raise;
  end;
end;

这必须是一个异常处理程序,调用Free并重新加注,因为代码无法使用finally。来电者会这样做:

Obj := CreateNewObject(...);
try
  ....
finally
  Obj.Free;
end;

查看问题中的代码,似乎是使用了我的列表中的第4项和第6项。但请注意,GetOffersList的实施并非异常安全。但是没有迹象表明这是问题所在。调用GetOffersList的代码无法破坏容器似乎是合理的。

你为什么要漏字?那么,字符串是托管对象。它们被引用计数,您不需要采取任何明确的行动来销毁它们。但是,如果它们包含在其他类中,其实例被泄露,则包含的字符串也会泄露。因此,集中精力修复物体的泄漏,并且您将处理字符串泄漏。

对于它的价值,TOffer对我来说感觉更像是值类型而不是引用类型。它没有方法,包含三个简单的标量值。为什么不把它作为记录并使用TList<TOffer>


那么,你如何进行? FastMM泄漏报告是您所需要的。您需要完整的FastMM而不是减少Embarcadero版本。它将识别与解除分配不匹配的分配。一个接一个地处理它们。

与此同时,研究高质量的代码。好的开源Delphi库将展示上面的所有模式,以及更多。向他们学习。

答案 1 :(得分:2)

String由编译器自动管理,您不需要手动释放它(除了在不适用于这种情况的罕见极端情况下)。 TObjectList具有OwnsObjects属性,您可以将其设置为True,以便列表自动为您释放对象。它的构造函数有一个可选的AOwnsObjects参数来初始化OwnsObjects属性。