FastMM4报错:"块头已经损坏"

6

我曾经遇到过一个很讨厌的错误,它在过去消失了,但现在又回来了。

我有两个TSam对象(派生自TPersistent),并被创建和加载到TAsmJob对象(派生自TObjectList)中。

在运行时,一个窗体创建了一个TStringGrid,然后创建了AsmJob,该作业创建了这两个SAM对象(并从磁盘中加载了一些数据)。 AsmJob也分配给了网格。 当窗体被销毁时,网格会通过释放AsmJob来处理它,这将释放TSam对象。 这里出现了问题:第一个对象没有问题地释放了,但是当调用其继承方法(在Destroy析构函数中)时,第二个对象就死了。

我在整个程序中使用FreeAndNil来释放对象。 TSam对象不是NIL! 因此,这是释放对象的第一次尝试。 即使对象内部的数据也是一致的。

程序的主要结构如下:

**Create:**

Form -> StringGrid
     -> AsmJob -> Sam1, Sam2
StringGrid.AsmJob:= AsmJob;


**Free:**

Form -> StringGrid -> AsmJob -> Sam1, Sam2

我真的不明白在对象被释放后我试图进行双重释放或覆盖的地方在哪里。
编辑:

我遇到的一些错误:

  • FastMM 在执行空闲块扫描操作期间检测到错误。FastMM 检测到一个块在被释放后已被修改。

  • FastMM 在执行空闲块扫描操作期间检测到错误。块头已经损坏。

详情:

The current thread ID is 0x19C, and the stack trace (return addresses) leading to this error is: 
402E77 [System][@FreeMem] 
4068DC [System][@DynArrayClear] 
405E2D [System][@FinalizeArray] 
405D31 [System][@FinalizeRecord] 
40432F [System][TObject.CleanupInstance] 
404272 [System][TObject.FreeInstance] 
404641 [System][@ClassDestroy] 
4D313E [UnitSam.pas][TSam.Destroy][297] 
4042BF [System][TObject.Free] 
4149ED [SysUtils][FreeAndNil] 
4D9C0A [UnitAsmJob.pas][UnitAsmJob][TAsmJob.Destroy][180]  

我已经在IDE中启用了所有的“调试”选项,包括“范围检查”。另外,FastMM4被设置为超级严格的调试模式。没有FastMM或者在调试器之外运行时,程序运行得很好 - 但我知道这并不意味着错误不再存在。实际上,它(可能)已经运行了一年以上,直到我安装了FastMM。

编辑:

谢谢大家。现在我感觉我正在朝着正确的方向前进。

程序的结构比较复杂,我只提供了骨干内容以使原帖保持简短。但是,算了,它已经变得更长了 :) 所以,这些TSam对象用于从磁盘加载数据。每个对象对应一个文件。它们还执行一些处理和数据验证。对于每个TSam对象,我还有一个图形对象,以图形方式显示屏幕上的数据。TStringGrid中的每一行也以文本方式显示TSam中的数据。

我有一个问题:如果我将程序分解成更小的部分以找出错误在哪里,这个错误仍然会出现吗?还是可能仅在特定配置中出现?


回答“如何将AsmJob分配给TStringGrid,以使TStringGrid销毁AsmJob,你能给我们展示吗?”

MyGrid = TStringGrid
  public 
    AsmJob: TAsmJob; 
  end; 

然后在 TForm.Create 中(这个窗体包含了 Grid),我执行以下操作

MyGrid.AsmJob=AsmJob; 

在 MyGrid 的析构函数中,我执行以下操作:
begin 
  FreeAndNil(AsmJob); 
  inherited 
end;
4个回答

14

这个错误意味着你的代码破坏了内部内存管理器的结构。你的调用堆栈表示了内存管理器检测到错误的位置。这不是错误路径或与之相关的任何内容。实际错误发生在此之前。

你应该尝试使用“范围检查错误”选项(别忘了进行“Build”,而不是“Compile”),以及启用CheckHeapForCorruption、CatchUseOfFreedInterfaces和DetectMMOperationsAfterUninstall选项的FullDebugMode下的FastMM。

你也可以全局开启FullDebugModeScanMemoryPoolBeforeEveryOperation变量,这样一旦问题出现就会立即得到一个错误提示,但是这个选项会大大减慢你的执行速度。

最好的选择可能是定期调用ScanMemoryPoolForCorruptions函数。只在一个地方调用它。出现错误了吗?那么更早地调用它。还是有错误?再次更早地调用它。没有错误?那么你的问题就在最后两次调用之间。现在你可以打开FullDebugModeScanMemoryPoolBeforeEveryOperation变量来获得精确的位置信息。只需要在此代码区域中打开它,然后在完成后关闭它。

还有一个非常相似的错误:“FastMM检测到在释放后修改了块”。在这种情况下,你的代码修改的是未使用的内存(“空闲内存”),而不是内部结构。

顺便说一句,你的错误并不是双重释放!如果这是一个双重释放调用,FastMM会明确告诉你(因为你试图释放未使用或不存在的内存块):“尝试释放/重新分配未分配的块”。


谢谢Alexander。我对“ScanMemoryPoolForCorruptions”一无所知。我猜这是FastMM DLL提供的一个函数。我现在会去搜索一下它。 - Gabriel
这是来自标准FastMM4.pas的函数。它来自完整独立版本的FastMM。它在集成到Delphi中的FastMM版本中不存在。这里没有DLL的问题。这只是一个通常的pas文件中的函数 ;) - Alex
1
很遗憾,链接已失效。但您可以在以下网址访问:http://web.archive.org/web/20091007162116/http://blog.eurekalog.com/?p=198 - EMBarbosa

4

块头损坏通常意味着某些不安全的操作正在覆盖内存,可能是使用了原始指针或汇编代码。如果在任何任务中使用了原始指针或汇编代码,请检查一下。此外,如果关闭了范围检查和边界检查,请尝试打开它们并重新构建。它们可以帮助捕获许多此类问题。


嗨,梅森。 没有汇编,没有原始指针操作,范围检查始终开启。 这正是我今天整天试图弄清楚的:我可能在哪里覆盖了该死的对象。 - Gabriel
1
我曾经遇到过类似的情况。最终发现是我使用的第三方库在处理原始指针时出了一些问题。这里可能也是这种情况吗? - Mason Wheeler
顺便说一句,看起来不像是对象被覆盖了,而是内存块头被覆盖了。但为了确保,请尝试在清理代码中设置断点,以便在析构函数运行之前检查此对象的状态。在调试器中查看它,并确保其字段有效。(如果无效,则可以使用地址断点轻松找到覆盖内存的内容。) - Mason Wheeler

2

代码中可能存在逻辑竞争的情况,即在释放对象时正在写入该对象。请添加NULL检查和其他IPC机制(锁定列表等),以确保不会出现这种情况。

另一种选择可能是对代码进行子类化以添加日志记录 - 并检查是否按顺序访问对象。


1

有几件事情我想问,因为我看不到你的代码。

给定以下代码:

procedure TForm1.FormCreate(Sender: TObject);
var
   wObjLst : TObjectList;
begin
   wObjLst := TObjectList.Create;
   try
      wObjlst.OwnsObjects := true;
      wObjlst.Add(TPersistent.Create);
      wObjlst.Add(TPersistent.Create);
   finally
      freeandnil(wObjlst);
   end;
end;

这个没有错误。

你说

在运行时,一个表单创建了一个TStringGrid和AsmJob,它创建了这两个SAM对象(并在每个对象中加载一些数据)。 AsmJob也分配给了网格。当表单被销毁时,网格通过释放AsmJob来处理它,从而释放TSam对象。问题在于:第一个对象可以无问题地释放,但是当调用其继承方法(在Destroy析构函数中)时,第二个对象就会死亡。

我的第一个问题是AsmJob如何分配给TStringGrid,以便TStringGrid销毁AsmJob,你能给我们展示一下吗?

其次,为什么要创建TObjectList的后代来存储两个对象,然后释放它们,而不是像上面所示那样自己创建它们并让TObjectList销毁它们。

另一个尝试的方法是从fastmm.sourceforge.net下载完整的FastMM4包,安装并使用fulldebug dll来跟踪确切失败的对象。你和我都假设它是SAM对象之一,但它可能是也可能不是。


嗨,Ryan。最初,ObjectList被设置为OwnObj=true,但现在我手动释放对象以查看错误出现的位置。这就是我确定错误出现在TSam对象的继承调用(destroy)中的方法。无论如何!如果我创建并使用TSam对象而不手动释放(这意味着没有TAsmJob - 这是这些对象的管理器),一切都非常完美,没有任何错误。-------- PS:我已经将FastMM设置为完全调试模式。 - Gabriel

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