Delphi字符串泄漏

7
我正在使用Delphi XE编写一个应用程序,该应用程序使用RemObjects SDK进行通信(这可能与问题有关)。我开启了FastMM调试模式,有时候(并不总是)当我关闭应用程序时,会出现关于单个“意外内存泄漏”的警告。“发生了意外的内存泄漏。意外的小块泄漏包括:117-124字节:UnicodeString x 1”。很少情况下,我会报告x2。

现在,我的理解是字符串是引用计数的,由于没有其他对象参与,导致内存泄漏的原因是什么?在这个StackOverflow问题中,人们找不到造成内存泄漏的方法。

如果没有明显的方法,那么我将下载最新的FastMM源代码(它似乎没有包含在XE源代码中)。

[解决后编辑]找到这个问题的解决方案是安装FastMM源代码,并启用FullDebugMode以获取堆栈跟踪。


我发现AQTime比Fast MM全调试模式更适用于查找内存泄漏。这正是因为FastMM报告此类泄漏的方式(首先告诉您有一个字符串泄漏,而不是包含泄漏字符串内存的对象)使我更喜欢使用AQTime。 - Warren P
@Warren,FastMM也会告诉你拥有该字符串的对象。这是我记忆中的内容,但实际上我已经很久没有出现内存泄漏了! - David Heffernan
@jachguate 问题是问号前面的那一位。@David,没有提到其他项目。当我泄漏一个类时,我会得到类类型和它包含的任何字符串。 - mj2008
按照我的回答方式操作,你就能得到堆栈跟踪信息。我不记得具体设置,但你可以从FastMM的文档中找到解决方法。 - David Heffernan
4个回答

8

当使用类型常量并且取决于终结顺序时,FastMM 可能会报告泄漏,而实际上并没有泄漏。

FastMM: 在我认为不应该有泄漏的地方报告了泄漏内存。

简而言之,当 FinalizedFirst 单元被终结时,SString 常量就会被释放。在单元的终结完成后,FinalizedLast 的终结将被调用。在终结中,它调用 FinalizedFirst 的 LeakMemory 方法。SString 变量再次被初始化,并且不会被释放,因为 FinalizedFirst 的终结已经运行过了。

FinalizedLast Unit

unit FinalizedLast;    

interface

uses FinalizedFirst;

implementation

initialization LeakMemory;
finalization LeakMemory;
end.

最终确定的第一个单元

unit FinalizedFirst;

interface

procedure LeakMemory;

implementation

uses FinalizedLast;

procedure LeakMemory;
const
  SString: string = '';
begin
  //***** SString will get initialized once or twice depending on the
  // finalization order of units. If it get's initialized twice,
  // a memory leak is reported.
  if SString = '' then
  SString := 'FooBar';
end;
end.

项目名称:泄漏内存。
program LeakMemory;
uses
FastMM4 in 'FastMM4.pas',
Forms,
FinalizedFirst in 'FinalizedFirst.pas',
FinalizedLast in 'FinalizedLast.pas';

{$R *.RES}
begin

Application.Initialize;
Application.Run;

end.

6

如果使用FreeMem释放堆上的记录而不是Dispose,或者使用System.Move或FillChar覆盖记录,则可能会泄漏字符串。在第一种情况下,终结代码不会运行;在第二种情况下,如果字符串字段已经被填充为nil,则它将认为已经清除了该字段。

如果您想要找到泄漏位置,请下载FastMM并打开FullDebugMode。它将包括泄漏发生位置的堆栈跟踪。


我这里没有做任何花哨的东西 - 没有记录,也没有手动内存管理。 - mj2008
一个未被释放的类对象,其中包含字符串?一个动态字符串数组,在没有设置长度为0的情况下? - Warren P
@Warren:第一个也会显示类作为泄漏。动态数组也是引用计数的,所以第二个不应该有影响。 - Zoë Peterson
1
@mj2008:我模糊地记得 Delphi 的一个版本(2006年?)如果你在 finalization 块中使用全局变量块,可能会泄漏字符串。这应该是在 2007 年或之前,而且自从我遇到这个问题以来已经过了很长时间,所以如果它是在 2007 年,那么它肯定已经在更新中修复了。 - Zoë Peterson

6

通常情况下,当字符串泄漏时,它们包含在其他未正确销毁的对象中。例如,一个未被释放的对象。然而,您也应该期望看到报告该对象。

解决方法是下载并使用完整版本的FastMM,并配置它在检测到泄漏时报告堆栈跟踪。这样做可以得到分配泄漏对象的完整堆栈跟踪,此时通常就清楚了问题所在。


5
唯一我能想到的不需要故意破坏字符串(如手动增加引用计数或进行一些混乱的指针操作)就可以泄漏字符串的方法是使用threadvar。
正如帮助文件所述,
“通常由编译器管理的动态变量(长字符串、宽字符串、动态数组、变体和接口)可以声明为threadvar,但编译器不会自动释放每个执行线程创建的堆分配内存。如果在线程变量中使用这些数据类型,则您有责任在线程终止之前从线程内部处理它们的内存。”
除此之外,没有其他想法未被提及。
[编辑者注] 这确实是问题所在,具体代码如下:
threadvar
    g_szAuthentication : String;


procedure TMyBase.SetAuthentication(szUserName, szPassword: String);
begin
    g_szAuthentication := '?name=' + szUserName + '&pass=' + szPassword;
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接