12

根据 FastMM4 的说法,我目前正在开发的 Delphi 程序泄漏了很多字符串。AnsiStrings 准确地说:

在此处输入图像描述

该应用程序 ( 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!?

这里有很多字符串泄漏问题,但没有一个真正阐明应该寻找什么模式。谷歌也不提供。

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

所以嗯,有什么想法吗?

4

5 回答 5

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 没有在对话框中显示堆栈跟踪并不意味着它缺少该信息。确保有FullDebugMode,LogMemoryLeakDetailToFile并且LogErrorsToFile在“FastMM4Options.inc”中定义。然后在可执行文件的目录中查找“[ExecutableName]_MemoryManager_EventLog.txt”文件。

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

--------------------------------2012/5/27 4:34:46-------- ----------------------
内存块已泄漏。尺寸为:12

分配此块时的堆栈跟踪(返回地址):
40305E
404B5D
404AF0
45C47B
43D726
42B0C3
42B1C1
43D21E
76C4702C [GetWindowLongW]
77AE3CC3 [RtlImageNtHeader 处的未知函数]

该块当前用于类对象:未知

分配编号为:484

从指针地址 7EF8DEF8 开始的 256 字节的当前内存转储:
01 00 00 ...
...

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

分配此块时的堆栈跟踪(返回地址):
40305E -> _GetMem
404B5D -> _NewAnsiString
404AF0 -> _LStrAsg
45C47B -> TForm1.Button4Click(在 FreeMem 行)
43D726 -> TControl.单击
...


编辑: 不是手动查找地址,而是通过链接器选项生成详细的地图文件,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,不太可能FreeMem,甚至不太可能FreeInstance释放 FastMM 在分配的内存泄漏时显示的对象/记录的内存可能会有所帮助。

于 2012-05-27T01:37:17.057 回答
4

你是对的,应该自动清理字符串。不过,我已经看到了几种解决方法。

第一个是如果你直接使用字符串数据结构做的事情会破坏引用计数。这是最有可能的,与您泄漏的字符串数量有关。

另一种是调用 Halt 并将字符串引用留在堆栈上。但是您不会在堆栈上留下 40,000 个字符串引用,因此我会寻找传递字符串然后调整其引用计数的代码。

于 2012-05-26T17:03:46.410 回答
1

简而言之,Delphi 内置字符串类型是引用计数的。内存分配和处置方法不负责更新引用计数,因此编译器不知道记录中的字符串实际上可以被释放。

不鼓励使用引用计数字符串类型定义记录。我以前也有同样的困惑。如果您查看 Delphi 库的源代码。你会发现很多记录的 PChar 不是字符串。

有些人讨论记录

于 2012-05-28T16:50:10.360 回答
1

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

例如,假设我在一个 TTreeView 中放入PMyRecord = ^MyRecord了 Node.Data。如果最后您循环遍历所有节点并简单地执行,Dispose(Node.Data)那么 MyRecord 中的任何字符串都将无法正确处理。

但是如果你通过调用明确地告诉编译器指针的底层类型是什么来处理你的指针Dispose(PMyRecord(Node.Data)),那么就不会有内存泄漏。

于 2017-01-26T06:07:44.733 回答
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;
于 2019-08-02T10:28:40.827 回答