这是一个内存泄漏吗?

3
快速内存管理器(FastMM)报告此行代码是内存泄漏的源头:
StrClassName := MidStr (curLine, length(START_OF_CLASSNAME)+1, length(curline)+1)
CopyMidStr出了什么问题?这只是Delphi 2007编译器的错误吗?还是较新版本也有这个问题?这里提供了FastMM报告的副本以及我应用程序显示此类报告的图像。为了在VirtualTreeView中显示节点,我需要一种新的数据类型。我称之为TMemoryLeak。当解析报告时,我给我的TMemoryLeak一个类名、调用堆栈、大小等等。但是当应用程序关闭并启动FastMM时,上面的复制行似乎会泄漏内存。我释放了堆栈、大小、整个对象,但是ClassName字段(一个字符串)总是会泄漏内存。更新(来自评论):以下是声明、构造函数和析构函数。至于生命周期-对象的析构函数在用于显示节点树的对象使用后立即调用。之后它们就过时了,被释放掉了(希望如此)。
TMemoryLeak = class(TObject)
    private
      fID              :integer;
      fSize            :integer;
      fTotalSize       :integer;
      fCallStack       :TStringList;
      fClassName       :string;
      fRepeatedInstance:integer;


    public
      property ID               :integer      read fID                write fID;
      property TotalSize        :Integer      read fTotalSize         write fTotalSize;
      property Size             :integer      read fSize              write fSize;
      property CallStack        :TStringList  read fCallStack         write fCallStack;
      property ClassName        :string       read fClassName         write fClassName;
      property RepeatedInstance :integer      read fRepeatedInstance  write fRepeatedInstance;
      class function Equal(xA: TMemoryLeak; xB: TMemoryLeak): Boolean;
      procedure clear;
      constructor create;
      destructor destroy; override;
  end;

  TMemoryLeakList=class(TObjectList)
    private
      fSortType         :TMlSortType;
      fSortDirection    :TMLSortDirection;
      fTotalLeakSize    :integer;
      fClassName        :string;
      fRepeatedInstance :Integer;
      fID               :Integer;
      function  GetItem(Index: Integer): TMemoryLeak;
      procedure SetItem(Index: Integer; const Value: TMemoryLeak);

    public
      property Items[Index: Integer]:TMemoryLeak      read GetItem             write SetItem; default;
      property TotalLeakSize        :integer          read fTotalLeakSize      write fTotalLeakSize;
      property SortType             :TMLSortType      read fSortType           write fSortType;
      property SortDirection        :TMLSortDirection read fSortDirection      write fSortDirection;
      property ClassName            :string           read fClassName          write fClassName;
      property RepeatedInstance     :integer          read fRepeatedInstance   write fRepeatedInstance;
      property ID                   :Integer          read fID                 write fID;

      function Add(AObject: TMemoryLeak): Integer;
      procedure Clear();
      procedure Sort;

      constructor create;
      destructor destroy; override;
  end;


constructor TMemoryLeak.create;
  begin
    inherited;
    fCallStack := TStringList.create;
    fRepeatedInstance:=0;
  end;
  destructor TMemoryLeak.destroy;
  begin
    clear;
  end;
  procedure TMemoryLeak.clear;
  begin
    fCallStack.Clear;
  end;
  class function TMemoryLeak.Equal(xA, xB: TMemoryLeak): Boolean;
  var i:Integer;
  begin
    Result:=False;

    if xA.ClassName = xb.ClassName then
    begin
      if xA.size = xb.size then
      begin
        if xA.CallStack.Count = xB.CallStack.Count then
        begin
          for i := 0 to xa.CallStack.Count - 1 do
          begin
            if CompareStr(xA.CallStack[i], xB.CallStack[i]) <> 0 then
            begin
             break;
            end;
          end;
          if i = xa.CallStack.Count then
            Result:=True;
        end
      end
    end

  end;

  { TMemoryLeakList }

  constructor TMemoryLeakList.create;
  begin
    inherited;
    fSortType         :=stID;
    fSortDirection    :=sdAsc;
    fClassName        :='';
    fRepeatedInstance :=0;
  end;
  destructor TMemoryLeakList.destroy;
  begin
    Clear;
  end;
  procedure TMemoryLeakList.Clear;
  var i : Integer;
  begin
      for i := 0 to Count - 1 do
        Items[i].clear;
  end;

请展示一个完整的程序,报告内存泄漏。 - David Heffernan
变量定义在哪里? - opc0de
马上就在编辑中。 - programstinator
1
这种情况是否发生在您在调试器窗口中观察的变量上?还是在IDE之外运行时也会发生? - Uli Gerhardt
@UliGerhardt 没有涉及到调试器。这种情况也会在IDE之外发生。 - programstinator
显示剩余5条评论
1个回答

11

合理的解释是您有一个内存泄漏。

我认为您对FastMM泄漏报告的工作原理存在误解。您似乎从泄漏报告中推断出Copy, MidStr等函数导致了内存泄漏。但事实并非如此。当内存被分配但之后没有释放时,才会报告内存泄漏。对于像CopyMidStr这样的函数,它们的工作是创建新的字符串,这自然涉及到内存的分配。泄漏之所以会报告,是因为用于保存字符串缓冲区的内存未被释放。当其余代码未能释放该内存时,CopyMidStr并不是分配函数的问题。

Delphi 2007是成熟的产品,字符串的内存管理已经被证明是正确的。也许您执行了一些手动的内存复制,绕过了字符串的引用计数。您是否使用FillChar将某些变量/字段设置为nil?您是否使用FreeMem而不是Dispose来处置记录?前者不会减少字符串的引用计数。类似这样的操作很可能是泄漏的原因。


看了您发布的代码片段,这是一个问题:

destructor TMemoryLeakList.destroy;
begin
  Clear;
end;

你未能调用继承的析构函数,这意味着列表成员将不会被销毁。这就解释了为什么你的字符串没有被销毁。

实际上,你不需要为列表类提供一个析构函数。只需删除它并让继承的TObjectList析构函数完成工作即可。由于OwnsObjects默认为True,任何列表的成员在从列表中移除时都会被销毁,并在列表本身被销毁时一起销毁。

如果你的Clear方法真正清空了该列表,那么这个问题就不会出现。但是你的Clear不是一个真正的Clear。容器中真正的Clear应该移除所有成员。你应该删除你的Clear并依赖继承版本。

TMemoryLeak中,你也没有调用继承的析构函数。并且也没有销毁该类所拥有的字符串列表实例。

综上所述,我会像这样编写这些构造函数和析构函数:

constructor TMemoryLeak.Create;
begin
  inherited;
  fCallStack := TStringList.Create;
end;

destructor TMemoryLeak.Destroy;
begin
  fCallStack.Free;
  inherited;
end;

constructor TMemoryLeakList.Create;
begin
  inherited;//by default OwnsObjects is set to True, list manages member lifetime
  fSortType :=stID;
  fSortDirection :=sdAsc;
end;

然后删除 destructorClear 方法。从 TObjectList 继承的版本是足够的。

你在评论中说:

对象的析构函数会在用于显示节点树时立即被调用。之后它们就过时了,并且已经被释放了(我希望是这样)。

我认为这样做很可能没有帮助。因为您在 OwnsObjects 模式下创建了对象列表,所以根本不应该销毁列表成员。您已经要求列表本身执行此操作。您不能同时执行两者。而且,“我希望”的注释并没有让我对这段代码正确性充满信心。

由于我们无法看到您的所有代码,因此我远非确定这是其全部问题。

最重要的是,您的代码存在泄漏。相信 FastMM!


1
FastMM并不暗示它们是罪魁祸首,仅仅是这些函数分配了内存。问题在于你的程序未能释放内存。FastMM并不试图找出为什么你的程序没有释放。它无法做到这一点。 - David Heffernan
4
你现在考虑的是“为什么这段内存被分配到了这里?”但这是错误的角度。相反,应该想一想,“为什么这段内存没有随后被释放?” - David Heffernan
1
@Goran_Mandic,我在你的问题中添加了代码和上面的评论,希望你不介意。 - LU RD
1
TMemoryLeakList.Destroy中出现了相同的错误。没有Inherited调用。 - LU RD
2
@Goran_Mandic 我在这里完成了。我相信你有一个泄漏问题。我已经分析了你展示的代码。我不需要处理你整个代码库的问题。 - David Heffernan
显示剩余8条评论

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