3

我有一些我没有编写的代码,但是存在内存泄漏。真正奇怪的是,如果我在返回之前将结构归零,内存只会泄漏。

可重现的最小代码

在 Delphi 5 和 Delphi 7 中泄漏是可重现的。

首先我们有一个结构:

type
   TLocalFile = packed record
      FileName: AnsiString;
   end;

这个结构是CollectionItem对象的私有成员:

TEntry = class(TCollectionItem)
private
   FLocalFile: TLocalFile;
end;

然后我们拥有拥有的集合,它有一个可以返回填充结构的函数:

TEntries = class(TCollection)
protected
    function GetLocalFile: TLocalFile;
public
    procedure DoStuff;
end;

奇怪的是位于GetLocalFile函数中:

function TEntries.GetLocalFile: TLocalFile;
var
    s: AnsiString;
begin
    //Only leaks if i initialize the returned structure
//  FillChar(Result, SizeOf(Result), 0);
    ZeroMemory(@Result, SizeOf(Result));

    s := 'Testing Leak';
    Result.Filename := s; //'Testing leak';  only leaks if i set the string through a variable
end;

实际上,这个函数被传递了一个流,并返回一个填充的结构,但现在这并不重要。

接下来我们有一个集合的方法,它将填充它的所有条目的LocalFile结构:

procedure TEntries.DoStuff;
var
    x: Integer;
begin
    for X := 0 to Count-1 do
    begin
       (Items[X] as TEntry).FLocalFile := GetLocalFile;
    end;
end;

最后,我们构建一个集合,向其中添加 10 个项目,拥有它们DoStuff,然后释放列表:

procedure TForm1.Button1Click(Sender: TObject);
var
    list: TEntries;
    i: Integer;
    entry: TCollectionItem;
begin
    list := TEntries.Create(TEntry);
    try
        for i := 1 to 10 do
            entry := list.Add;

        list.DoStuff;
    finally
        list.Free;
    end;
end;

我们创造了10 个项目,我们泄露了9 个 AnsiStrings

令人恐惧的令人困惑的事情

有一些方法可以使此代码不泄漏。它仅在使用中间字符串堆栈变量时泄漏

改变:

function TEntries.GetLocalFile: TLocalFile;
var
   s: AnsiString;
begin
   s := 'Testing Leak';
   Result.Filename := s; //'Testing leak';  only leaks if i set the string through a variable
end;

function TEntries.GetLocalFile: TLocalFile;
begin
   Result.Filename := 'Testing leak'; //doesn't leak
end;

它不会泄漏。

另一种方法在返回之前

删除对FillCharor的调用ZeroMemory,它不会泄漏:

function TEntries.GetLocalFile: TLocalFile;
var
    s: AnsiString;
begin
    //Only leaks if i initialize the returned structure
//  FillChar(Result, SizeOf(Result), 0);
//  ZeroMemory(@Result, SizeOf(Result));

    s := 'Testing Leak';
    Result.Filename := s; //'Testing leak';  only leaks if i set the string through a variable
end;

这些都是奇怪的决议。无论我是否使用中间堆栈变量,无论我是否将结构归零,都不会对内存清理产生任何影响。

我怀疑这是编译器中的错误。这意味着我(意思是写这篇文章的人)正在做一些根本错误的事情。我认为这与TCollectionItemClass。但我无法为我的生活弄清楚是什么。

完整代码

unit FMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
    TLocalFile = packed record
        FileName: AnsiString;
    end;

    TEntry = class(TCollectionItem)
   private
        FLocalFile: TLocalFile;
    end;

    TEntries = class(TCollection)
    protected
        function GetLocalFile: TLocalFile;
    public
        procedure DoStuff;
    end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
  public
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
    contnrs;

procedure TForm1.Button1Click(Sender: TObject);
var
    list: TEntries;
    i: Integer;
    entry: TCollectionItem;
begin
    list := TEntries.Create(TEntry);
    try
        for i := 1 to 10 do
        begin
            entry := list.Add;
        end;

        list.DoStuff;
    finally
        list.Free;
    end;
end;

{ TEntries }

procedure TEntries.DoStuff;
var
    x: Integer;
    entry: TEntry;
begin
    for X := 0 to Count-1 do
    begin
        entry := Items[X] as TEntry;
        entry.FLocalFile := GetLocalFile;
   end;
end;

function TEntries.GetLocalFile: TLocalFile;
var
    s: AnsiString;
begin
    //Only leaks if i initialize the returned structure
//  FillChar(Result, SizeOf(Result), 0);
    ZeroMemory(@Result, SizeOf(Result));

    s := 'Testing Leak';
    Result.Filename := s; //'Testing leak';  only leaks if i set the string through a variable
end;

end.

哦,别忘了添加FastMM4到你的项目中(如果你还没有内置它),这样你就可以检测到泄漏。

在此处输入图像描述

4

1 回答 1

9

AnsiString (and its Unicode counterpart) is reference-counted by the compiler. You can't simply zero memory containing a reference to it; you need to assign '' to it so that the compiler will generate code to decrement the refcount and release the memory correctly.

You'll have similar problems trying to block-clear data structures containing references to dynamic arrays, Interfaces, or (some) variants.

If you're not using a Delphi version recent enough to support the Default compiler magic expression, (I believe it was introduced in D2009,) the best way to clear out a record safely would be to call Finalize first, and then zero the memory as you're doing.

于 2014-03-20T21:56:13.817 回答