我有一些我没有写的代码,但是有一个内存泄漏。真正奇怪的是,只有当我在返回结构之前使结构为零时,内存才会泄漏。
可复制最小码
这个漏洞在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或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;这些都是奇怪的决心。无论我是否使用中间堆栈变量,无论我是否对结构进行零化,都不应该对内存清理产生任何影响。
我怀疑这是编译器中的一个bug。这意味着我(指写这篇文章的人)做了一些根本错误的事情。我认为这与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添加到您的项目中(如果您还没有内置的话),这样您就可以检测泄漏了。

发布于 2014-03-20 21:56:13
AnsiString (及其Unicode对应项)是由编译器计算的引用。您不能简单地将包含对它的引用的内存为零;您需要将''分配给它,这样编译器将生成代码以减少折算并正确释放内存。
如果试图阻塞包含对动态数组、接口或(某些)变体的引用的数据结构,您将遇到类似的问题。
如果您没有使用最近的Delphi版本来支持Default编译器魔术表达式(我相信它是在D2009中引入的),安全清除记录的最好方法是首先调用Finalize,然后像您正在做的那样将内存为零。
https://stackoverflow.com/questions/22545930
复制相似问题