这是怎么内存泄漏的?
Posted
技术标签:
【中文标题】这是怎么内存泄漏的?【英文标题】:How is this a memory leak? 【发布时间】:2012-11-14 13:30:17 【问题描述】:FastMM 将此行报告为内存泄漏的来源:
StrClassName := MidStr (curLine, length(START_OF_CLASSNAME)+1, length(curline)+1)
Copy
和 MidStr
怎么了?这只是Delphi 2007编译器的bug,还是以后的版本也有这个问题?
这是 FastMM 报告副本的link,以及我的应用程序如何显示这些类型的报告的image。看,为了显示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;
【问题讨论】:
请显示报告泄漏的完整程序。 变量定义在哪里? 即将在编辑中出现。 @opc0de 该变量在解析报告的过程中是本地的。不是变量,而是AnsiString
用于泄漏内存的Copy
/MidStr
方法。
您在调试器窗口中查看的变量是否会发生这种情况?或者在 IDE 之外运行时也会发生这种情况?
【参考方案1】:
有道理的解释是你有内存泄漏。
我认为您对 FastMM 泄漏报告的工作原理存在误解。您似乎从泄漏报告中推断出Copy
、MidStr
等是造成泄漏的原因。事实并非如此。当内存被分配但随后没有被释放时会报告泄漏。对于像Copy
和MidStr
这样的函数,它们的工作是创建新的字符串,这自然会涉及到内存的分配。报告泄漏是因为为保存字符串缓冲区而创建的内存未释放。当其余代码未能释放该内存时,这不是分配函数Copy
或MidStr
的错。
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;
然后删除destructor
,并删除Clear
方法。从TObjectList
继承的版本就足够了。
在您声明的 cmets 中:
只要对象用于显示节点树,就会调用对象的析构函数。之后它们就过时了,并被释放(我希望如此)。
我想说这很有可能没有帮助。由于您在OwnsObjects
模式下创建了对象列表,因此您根本不应该破坏列表的成员。您已经要求列表本身执行此操作。你们两个都做不到。而“我希望”的评论并没有让我对这段代码是正确的充满信心。
由于我们看不到您的所有代码,我不能确定这就是它的全部问题。
底线是您的代码存在泄漏。相信 FastMM!
【讨论】:
FastMM 并不认为他们是罪魁祸首。仅仅是那些函数分配了内存。失败是您的程序无法解除分配。 FastMM 不会尝试找出您的程序未释放的原因。它不能这样做。 您正在考虑“为什么要在此处分配此内存?”但这是错误的观点。而是想“为什么这个内存没有随后被释放?” @Goran_Mandic,我在你的问题中添加了代码和上面的评论,希望你不介意。TMemoryLeakList.Destroy
中的相同错误。没有Inherited
电话。
@Goran_Mandic 我在这里完成了。我确定你有泄漏。我已经挑选了您显示的代码。你的整个代码库不是由我来工作的。以上是关于这是怎么内存泄漏的?的主要内容,如果未能解决你的问题,请参考以下文章