【问题标题】:What is Causing This Memory Leak in Delphi?是什么导致了 Delphi 中的这种内存泄漏?
【发布时间】:2009-12-29 22:32:40
【问题描述】:

我只是无法弄清楚 EurekaLog 为我的程序报告的内存泄漏。我正在使用 Delphi 2009。这里是:

Memory Leak: Type=Data; Total size=26; Count=1;
The stack is:
System.pas  _UStrSetLength  17477
System.pas  _UStrCat           17572
Process.pas  InputGedcomFile  1145

这就是堆栈中的全部内容。 EurekaLog 将我指向第一次分配未释放内存的位置。据此,我的程序中的行是 InputGedcomFile 的第 1145 行。那行是:

CurStruct0Key := 'HEAD' + Level0Key;

其中 CurStruct0Key 和 Level0Key 在过程中被简单地定义为局部变量,在进入和离开过程时应该由 Delphi 内存管理器动态处理:

var CurStruct0Key, Level0Key: string;

所以现在我看一下系统单元中的 _USrCat 过程。第 17572 行是:

CALL    _UStrSetLength  // Set length of Dest

然后我进入System Unit中的_USrSetLength过程,相关行是:

@@isUnicode:
        CMP     [EAX-skew].StrRec.refCnt,1 // !!! MT safety
        JNE     @@copyString  // not unique, so copy

        SUB     EAX,rOff      // Offset EAX "S" to start of memory block
        ADD     EDX,EDX       // Double length to get size
        JO      @@overflow
        ADD     EDX,rOff+2    // Add string rec size
        JO      @@overflow
        PUSH    EAX           // Put S on stack
        MOV     EAX,ESP       // to pass by reference
        CALL    _ReallocMem
        POP     EAX
        ADD     EAX,rOff      // Readjust
        MOV     [EBX],EAX     // Store
        MOV     [EAX-skew].StrRec.length,ESI
        MOV     WORD PTR [EAX+ESI*2],0 // Null terminate
        TEST    EDI,EDI       // Was a temp created?
        JZ      @@exit
        PUSH    EDI
        MOV     EAX,ESP
        CALL    _LStrClr
        POP     EDI
        JMP     @@exit

其中第 17477 行是“CALL _ReallocMem”行。

那么什么是内存泄漏?当然,字符串常量与本地字符串变量的简单连接不应该导致内存泄漏。

为什么 EurekaLog 将我指向 Delphi 的 _UStrSetLength 例程中的 ReallocMem 行?

这是 Delphi 2009,我正在使用新的 unicode 字符串。

这里的任何帮助或解释将不胜感激。


找到的解决方案:

字符串被分配给新菜单项的属性。菜单项已添加到菜单中。然后新的菜单项被释放,因此我认为一切都已清理干净。因为字符串(通过引用计数)仍然被使用,因为它被复制到项目中,所以字符串没有在内存中释放,即使它是作为例程中的局部变量引入的。

通常这不应该是泄漏。但是,我的程序有时会使用“menu.item.delete(i)”删除各种菜单项。我没有意识到删除不会释放项目本身的内存。因此,引用计数的字符串没有被释放,从而导致泄漏。

解决方案是将我的“menu.item.delete(i)”语句更改为:“menu.item[i].free”。

【问题讨论】:

  • 这是报告中唯一的泄漏吗?
  • 不,还有很多其他的。但是他们中的大多数都像这个,这似乎表明 _ReallocMem 是一个问题。我以这个特定的例子为例,因为我看不出我的代码是如何导致这种情况的。
  • 有一个很好的方法可以追踪内存泄漏,这与您正在做的事情相反。不要看你泄露最多的变量类型,看看你泄露最少的类型,因为它很可能会拥有一堆其他的东西你在泄漏。先修复这个泄漏,然后看看你的内存泄漏列表变小了多少。
  • 似乎有一种“艺术”来堵漏。我必须先从静态艺术开始,然后再转向肖像。
  • 不。编程涉及很多艺术,但泄漏跟踪是一门精确的科学。基本规则是每块内存都应该有一个所有者(或者是一个数据结构,比如一个对象,或者一个引用计数系统,或者一个代码块),并且所有者负责清理它的所有变量。不再需要时拥有。从这两个规则中,您可以通过一些分析得出从所有权树开始尽可能高的原则。 :)

标签: delphi delphi-2009


【解决方案1】:

它会将您指向分配了相关内存的行。你是对的,字符串的处理应该由编译器处理。您可能遇到了两件事中的其中一件。

  1. 这不是您泄漏的唯一内容。该字符串属于一个永远不会被清理的对象。例如,CurStruct0Key 可能会被传递给构造函数,在构造函数中它被分配给对象。或者
  2. 您正在对字符串进行一些奇怪的操作,例如将其转换为指针并传递它。不要这样做!它可以阻止引用计数!

检查这些事情是否是导致您的问题的原因。

【讨论】:

  • 感谢这些想法。我会检查他们。
  • +1:记住,字符串是引用计数的,所以你的赋值将 ref 设置为 1。每当你传递该字符串时,它只会增加 ref。当您的局部变量超出范围时,它会减少引用计数。编译器只会在字符串减为 0 时释放该字符串。您可以将此字符串传递给类字段、全局变量、记录结构等。任何仍持有引用的内容都将阻止它被释放。
  • 感谢梅森的回答。昨晚我注意到一个连续运行的应用程序内存无限增长。
  • (续)但在终止时没有报告内存泄漏。我一直在重构它,但错过了这种行为开始的时间,所以它可能在任何地方。您的回答使我想到了我显然在 Stupids 攻击期间添加的一行代码。我有几个动态记录数组,记录包含字符串。我将每个数组传递给一个初始化例程,并以某种方式决定在重用它之前将数组填零是明智的。呵呵!
【解决方案2】:

获得像 AQTime 这样更好的工具,您的“查找内存泄漏”问题就会变得更简单。 通常,我尝试查找所有类泄漏(TSomething)并忽略 String 类型数据,因为正如上面有人指出的那样,如果某个泄漏的记录或类类型引用了该字符串,则该字符串不会被释放。整理你的课程和你的记录,字符串泄漏都会为你修复。

【讨论】:

  • 这似乎是我遗漏的一个关键概念。我会调查的。
【解决方案3】:

我在我的 Delphi 项目中遇到了内存泄漏。此过程在我的机器上运行良好,但在网络上登录的机器上却失败了。

procedure accTrimWorkingSet;
var
 hProcess: THandle;
begin
 hProcess:=OpenProcess(PROCESS_SET_QUOTA, false, GetCurrentProcessId);
 try
   SetProcessWorkingSetSize(hProcess, $FFFFFFFF, $FFFFFFFF);
 finally CloseHandle(hProcess); end;
end;

原来我使用的是映射潜水。当我更改为 UNC 路径时,该过程有效。

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多