Delphi应用程序泄漏AnsiStrings

时间:2012-05-26 16:53:29

标签: string delphi memory memory-leaks dev-c++

根据FastMM4,我正在研究的Delphi程序正在泄漏很多字符串。确切地说,AnsiStrings:

enter image description here

应用程序(http://sourceforge.net/projects/orwelldevcpp/)用于泄漏更多其他数据类型,但FastMM4可以报告实例的创建位置,因此我设法解决了这个问题。奇怪的是,FastMM4根本不报告这些泄漏的位置。

编辑:它似乎确实存在,请参阅修复的答案。无论如何,问题仍然存在:世界上我是如何泄漏这些东西的?

所以,嗯,不幸的是,我不知道该找什么。我的意思是,如果这些事情超出了范围,它们应该被自动释放(即使它们在堆上)?

我确实设法通过随机评论跟踪一些泄漏事件并查看计数会发生什么。这是一个例子:

// simply passing it a constant creates a leak...
MainForm.UpdateSplash('Creating extra dialogs...');

procedure TMainForm.UpdateSplash(const text : AnsiString);
begin
  if not devData.NoSplashScreen then // even if this branch is NOT taken
    SplashForm.Statusbar.SimpleText := 'blablabla' + text;
end;

// And even if the function call itself is placed within a NOT taken branch!

这是另一个泄漏的例子:

// Passing this constants produces leaks...
procedure TCodeInsList.AddItemByValues(const a, b, c: AnsiString;...);
var
  assembleditem : PCodeIns;
begin
   new(assembleditem);
   assembleditem^.Caption:=a;
   assembleditem^.Line:=b;
   assembleditem^.Desc:=c;
   ...
   fList.Add(assembleditem);
end;

// ... even when calling this on WM_DESTROY!
destructor TCodeInsList.Destroy;
var
  I: integer;
begin
  for I := 0 to fList.Count - 1 do
    Dispose(fList[I]);
  fList.Free;
  inherited Destroy;
end;

// produces leaks!?

这里有很多字符串泄漏问题,但没有一个真正澄清应该寻找的模式。谷歌也没有提供。

编辑:所以,我必须寻找传递的常量。但为什么呢?

嗯,任何想法?

5 个答案:

答案 0 :(得分:14)

您不需要显式分配字符串。除了使用引用计数进行修改之外,对象或记录的字符串字段也可能泄漏。例如,

type
  PRecord = ^TRecord;
  TRecord = record
    S: string;
  end;

procedure TForm1.Button4Click(Sender: TObject);
var
  r: PRecord;
begin
  GetMem(r, SizeOf(r^));
  Initialize(r^);
  r.S := ' ';
  FreeMem(r);

在上面的示例中,由于记录本身的内存已被释放,因此FastMM将仅报告泄漏的字符串。


无论如何,FastMM没有在对话框中显示堆栈跟踪并不意味着它缺少该信息。请务必在“FastMM4Options.inc”中定义FullDebugModeLogMemoryLeakDetailToFileLogErrorsToFile。然后在可执行文件的目录中查找“[ExecutableName] _MemoryManager_EventLog.txt”文件。

对于上面的示例,FastMM生成以下文件:

--------------------------------2012/5/27 4:34:46--------------------------------
A memory block has been leaked. The size is: 12

Stack trace of when this block was allocated (return addresses):
40305E 
404B5D 
404AF0 
45C47B 
43D726 
42B0C3 
42B1C1 
43D21E 
76C4702C [GetWindowLongW]
77AE3CC3 [Unknown function at RtlImageNtHeader]

The block is currently used for an object of class: Unknown

The allocation number is: 484

Current memory dump of 256 bytes starting at pointer address 7EF8DEF8:
01 00 00 ...
...

现在您可以运行该应用程序,暂停然后搜索地址。对于上述日志和测试应用程序,地址解析为:

Stack trace of when this block was allocated (return addresses):
40305E    -> _GetMem
404B5D    -> _NewAnsiString
404AF0    -> _LStrAsg
45C47B    -> TForm1.Button4Click (on FreeMem line)
43D726    -> TControl.Click
... 


修改 而不是手动查找地址,通过链接器选项生成详细的映​​射文件,FastMM将执行此操作(感谢Mason的评论)。


您对该问题的编辑反映了类似于上述示例中的泄漏。如果'fList'是常规TList,它只保存指针并且不知道这些指针指向的是什么。因此,当您释放指针时,只释放为指针本身分配的内存,而不释放记录的字段。因此,泄漏与传递给函数的常量无关,但就像下面的模式一样:

var
  assembleditem: PCodeIns;
  p: Pointer;
begin
  new(assembleditem);
  assembleditem^.Caption:='a';
  ..    
  p := assembleditem;
  Dispose(p);

对于要处理的记录,代码应该将指针指向其类型:

Dispose(PCodeIns(p));

所以你的'TCodeInsList.Destroy'应该是:

destructor TCodeInsList.Destroy;
var
  I: integer;
begin
  for I := 0 to fList.Count - 1 do
    Dispose(PCodeIns(fList[I]));
  fList.Free;
  inherited Destroy;
end;


最后,您正在寻找的模式似乎正在寻找代码意图释放具有字符串字段的记录(不太可能的对象)的地方。正在寻找Dispose FreeMemFreeInstance更不可能{{1}}释放FastMM在分配的内存泄露时显示的对象/记录的内存可能会有所帮助。

答案 1 :(得分:4)

你应该自动清理字符串。不过,我已经看到了一些方法来搞砸了。

首先,如果你直接使用字符串数据结构来破坏引用计数。这是最有可能的,你要泄漏的字符串数量。

另一个是调用Halt并在堆栈上留下字符串引用。但是你不会在堆栈上留下40,000个字符串引用,所以我会寻找传递一个字符串的代码,然后用它的引用计数来摆弄它。

答案 2 :(得分:1)

用简短的单词,Delphi内置字符串类型是引用计数。内存分配和dispose方法不会更新引用计数,因此编译器不知道您的记录中的字符串是否可以实际释放。

不建议使用引用计数字符串类型来定义记录。我之前有过同样的困惑。如果你看一下Delphi库的来源。你会发现很多记录都有PChar而不是字符串。

Some discuss about records

答案 3 :(得分:1)

泄漏字符串的最常见方法是使记录包含字符串和指向该记录的指针。如果您只是执行该指针的Dispose(),编译器将释放指针,而不是释放下方记录中的所有内容。始终确保您的配置代码告诉编译器您要处理的内容。

例如,假设在TTreeView中我将PMyRecord = ^ MyRecord放在Node.Data中。如果,最后你循环遍历所有节点,只需执行Dispose(Node.Data),那么MyRecord中的任何字符串都将无法正确处理。在这种情况下,您需要Dispose(PMyRecord(Node.Data))。

答案 4 :(得分:0)

我发现即使没有内存分配/指针操作,字符串(作为记录中的字段)也可能泄漏。

这听起来很疯狂,但至少在XE3中是这样。这是示例:

TMyRecord = record
x,
y: integer;
s: ansistring;
end;

function GetMyRec: TMyRecord;
begin
....
end;

....
procedure DoSomething;
var
  rec: TMyRecord;
begin
  ...
  rec := GetMyRec; //First call - everything is OK
  ...
  rec := GetMyRec; //Repeated call > Memory Leak of 
                   //Ansistring !!!!
  //To avoid the leak do the following BEFORE a 
  //repeated call: rec.s := unassigned;
end;