首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >这个内存泄漏的来源是什么(当使用TCollection时)

这个内存泄漏的来源是什么(当使用TCollection时)
EN

Stack Overflow用户
提问于 2014-03-20 21:52:34
回答 1查看 1.2K关注 0票数 3

我有一些我没有写的代码,但是有一个内存泄漏。真正奇怪的是,只有当我在返回结构之前使结构为零时,内存才会泄漏。

可复制最小码

这个漏洞在Delphi 5和Delphi 7中是可重复的。

首先,我们有一个结构:

代码语言:javascript
复制
type
   TLocalFile = packed record
      FileName: AnsiString;
   end;

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

代码语言:javascript
复制
TEntry = class(TCollectionItem)
private
   FLocalFile: TLocalFile;
end;

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

代码语言:javascript
复制
TEntries = class(TCollection)
protected
    function GetLocalFile: TLocalFile;
public
    procedure DoStuff;
end;

GetLocalFile函数中有一个奇怪的地方:

代码语言:javascript
复制
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结构:

代码语言:javascript
复制
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,然后释放列表:

代码语言:javascript
复制
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

令人毛骨悚然的事情

这段代码有一些不泄漏的方法。它只在使用中间字符串堆栈变量时泄漏。

更改:

代码语言:javascript
复制
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;

代码语言:javascript
复制
function TEntries.GetLocalFile: TLocalFile;
begin
   Result.Filename := 'Testing leak'; //doesn't leak
end;

而且不会漏水。

另一种方法是在返回结构之前先初始化而不是

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

代码语言:javascript
复制
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;

这些都是奇怪的决心。无论我是否使用中间堆栈变量,无论我是否对结构进行零化,都不应该对内存清理产生任何影响。

我怀疑这是编译器中的一个bug。这意味着我(指写这篇文章的人)做了一些根本错误的事情。我认为这与TCollectionItemClass有关。但我无法为我的生命想出什么。

全码

代码语言:javascript
复制
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添加到您的项目中(如果您还没有内置的话),这样您就可以检测泄漏了。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-03-20 21:56:13

AnsiString (及其Unicode对应项)是由编译器计算的引用。您不能简单地将包含对它的引用的内存为零;您需要将''分配给它,这样编译器将生成代码以减少折算并正确释放内存。

如果试图阻塞包含对动态数组、接口或(某些)变体的引用的数据结构,您将遇到类似的问题。

如果您没有使用最近的Delphi版本来支持Default编译器魔术表达式(我相信它是在D2009中引入的),安全清除记录的最好方法是首先调用Finalize,然后像您正在做的那样将内存为零。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22545930

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档