我有一些我没有编写的代码,但是存在内存泄漏。真正奇怪的是,如果我在返回之前将结构归零,内存只会泄漏。
可重现的最小代码
在 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;
它不会泄漏。
另一种方法不在返回之前
删除对FillChar
or的调用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
到你的项目中(如果你还没有内置它),这样你就可以检测到泄漏。