为动态数组分配内存 - 块头已损坏(FastMM4)

时间:2009-07-18 14:56:53

标签: delphi

我在1-2周前有一个话题是“块头已损坏”和“块被释放后被修改”错误。

有人给了我一个很好的建议(感谢亚历山大)关于将FullDebugModeScanMemoryPoolBeforeEveryOperation设置为true,最后我有一些迹象表明错误的TRUE位置在哪里。

错误日志指向TScObj对象。我有一个非常类似于这个的第二个对象,当我使用它时,错误不会出现。因此,这以某种方式确认错误在此特定对象(TScObj)中。

日志是这样的:

FastMM has detected an error during a free block scan operation. 
FastMM detected that a block has been modified after being freed. 
Modified byte offsets (and lengths): 15656(1)

The previous block size was: 15672
This block was previously allocated by thread 0xC88, and the stack trace (return addresses) at the time was:
402EC9 [System][@ReallocMem]
40666C [System][DynArraySetLength]
40A17D [FastMM4][UpdateHeaderAndFooterCheckSums]
40674E [System][@DynArraySetLength]
4CE329 [ReadSC.pas][ReadSC][TScObj.ReadData][239]
4CDD0C [ReadSC.pas][ReadSC][TScObj.LoadFromFile][168]
4D013E [SmplCubImport.pas][SmplCubImport][TCubImport.ImportSample][164]
40461A [System][@AfterConstruction]
4DC151 [UnitAsmJob.pas][UnitAsmJob][TAsmJob.LoadSample][960]

The allocation number was: 78709
The block was previously freed by thread 0xC88, and the stack trace (return addresses) at the time was:
402E6F [System][@FreeMem]
4068A8 [System][@DynArrayClear]
405DF9 [System][@FinalizeArray]
4CE9F9 [ReadSC.pas][ReadSC][TScObj.ReadData][298]
4CDD0C [ReadSC.pas][ReadSC][TScObj.LoadFromFile][168]
4D013E [SmplCubImport.pas][SmplCubImport][TCubImport.ImportSample][164]
40461A [System][@AfterConstruction]
4DC151 [UnitAsmJob.pas][UnitAsmJob][TAsmJob.LoadSample][960]

问题是我在代码中没有看到任何可能错误分配内存的地方。

type 
 TWordTrace  = array of Word;   
 TDiskTrc  = array of Smallint; 
 var Tracea,Tracec: TWordTrace;

procedure TScObj.ReadData;
Var i: Integer;
    DiskTrc1: TDiskTrc;
    DiskTrc2: TDiskTrc;
    DiskTrc3: TDiskTrc;
    DiskTrc4: TDiskTrc;
begin
 SetLength(DiskTrc1, H.NrOfSamples+1);
 SetLength(DiskTrc2, H.NrOfSamples+1);
 SetLength(DiskTrc3, H.NrOfSamples+1);
 SetLength(DiskTrc4, H.NrOfSamples+1);    <------ log shows error here. <- on DynArraySetLength

 FStream.Seek( H.SOffset, soFromBeginning);

 if H.SampleSize = 1 then
  begin
    for i:= 1 TO H.NrOfSamples DO
     FStream.Read( DiskTrc1[i], 1);
    Unpack(DiskTrc1);

    for i:= 1 TO H.NrOfSamples DO
     FStream.Read( DiskTrc2[i], 1);
    Unpack(DiskTrc2);

    etc...
  end

 else
  begin
   for i:= 1 TO H.NrOfSamples DO
    begin
     FStream.Read( DiskTrc1[i], 2);
     DiskTrc1[i]:= Swap(DiskTrc1[i]);
    end;
   Unpack(DiskTrc1);

   for i:= 1 TO H.NrOfSamples DO
    begin
     FStream.Read( DiskTrc2[i], 2);
     DiskTrc2[i]:= Swap(DiskTrc2[i]);
    end;
   Unpack(DiskTrc2);

   etc...
  end;

 SetLength(Tracea, H.NrOfSamples+1);   
 SetLength(Tracec, H.NrOfSamples+1);
 SetLength(Traceg, H.NrOfSamples+1);
 SetLength(Tracet, H.NrOfSamples+1);   <------ log shows error here. <- on FinalizeArray

 for i:=1 to H.NrOfSamples DO
   begin
     if DiskTrc1[i]< 0
     then Tracea[i]:= 0
     else Tracea[i]:= DiskTrc1[i];

     if DiskTrc2[i]< 0
     then Tracec[i]:= 0
     else Tracec[i]:= DiskTrc2[i];

     etc...
   end;
end;


procedure TScObj.Unpack(VAR DiskTrc: TDiskTrc);
var i: integer;
    Prev: Integer;
    Recover: Integer;
begin
 Prev:= 0;
 for i:= 1 to H.NrOfSamples do
   begin
     Recover := DiskTrc[i] + Prev;
     if (Recover>  32767) OR (Recover< -32768)
     then Recover:= 0;
     DiskTrc[i]:= Recover;
     Prev:= DiskTrc[i];
   end;
 Prev:= 0;
 for i:= 1 to H.NrOfSamples do
   begin
     Recover := DiskTrc[i] + Prev;
     if (Recover>  32767) OR (Recover< -32768)
     then Recover:= 0;
     DiskTrc[i]:= Recover;
     Prev:= DiskTrc[i];
   end;
end;

稍后在“从磁盘加载”过程中,来自临时加载器对象(SC)的信息被转换为更“明确”的对象,如下所示:

 TSam = class
 etc...                                                
 for i:= 1 to NrOfSamples DO
  begin
   CMX[i].Tracea:= SC.Tracea[i];
   CMX[i].Tracec:= SC.Tracec[i];
   etc...
  end;

编辑2: 只有当我尝试打开/加载一组非常特定的(两个)文件时,才会出现该错误。对于所有其他文件,错误不会显示。

7 个答案:

答案 0 :(得分:2)

您是否尝试在启用FullDebugModeScanMemoryPoolBeforeEveryOperation的情况下运行此代码?您是否尝试在TScObj.ReadData的开始时调用ScanMemoryPoolForCorruptions?

如果这没有帮助 - 尝试进入该问题调用(GetMem?)并按照FastMM的代码查看该损坏的标头的地址。只需将其写在纸上并重新启动程序即可。这个块的地址很可能是相同的。

在安全位置设置断点 - 即在“坏事”发生之前。一旦停在它上面 - 然后在内存的位置设置一个新的断点 - 就在这个标题处,稍后会被破坏(请确保不要过早设置)。

然后只运行你的程序 - 调试器将在这个错误代码处停止,该代码试图修改标题。

答案 1 :(得分:1)

我认为你在其他地方发生过腐败,看起来像常规电话就是解决问题。你所指的位置似乎不会破坏记忆。如果不仔细查看代码,隔离程序并仔细测试它们,就很难猜出问题所在。编译时会收到任何警告吗?

答案 2 :(得分:1)

我认为错误不是显示的代码,但是你应该检查一下:

SetLength(DiskTrc1, H.NrOfSamples+1);

  for i:= 1 TO H.NrOfSamples DO
     FStream.Read( DiskTrc1[i], 1);

这要归功于Setlength中的+1,但是你知道你在DiskTrc1 [0]上分配了一个从未使用过的额外项吗?

我怀疑你将它与Setlength(xx,N)混合在一起(CMX是如何创建/标注的?)。

请注意,正常模式是

SetLength(DiskTrc1, H.NrOfSamples);

  for i:= 0 TO H.NrOfSamples-1 DO
     FStream.Read( DiskTrc1[i], 1);

答案 3 :(得分:1)

您需要显示更多代码(例如,如何定义TDiskTrc?)

几点:

请记住,动态数组是引用计数的,您是否尝试使用和不使用优化运行?

在使用任何这些项目之前,您可能需要断言:

  assert(CMX <> nil);
  assert(Length(CMX) = NrOfSamples+1);

是否存在存储对数组的引用的代码(Swap,Unpack,...)?

您是否正在重复使用SC对象? (提示:不要)

您是否正在清除任何数组(如DiskTrc1:= nil或SetLength(DiskTrc1,0)?

答案 4 :(得分:1)

每个人似乎都在开始时跳过重要的错误:

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

某处正在使用旧指针。

由于你不知道你正在寻找的指针错误我现在会忘记它并去寻找另一个 - 它们可能会变得相关。

答案 5 :(得分:1)

解决。 今天我花了一天时间手动检查每行代码。我做了很少的改变,最后问题消失了。我没有试过看哪个特定的行产生了问题。

非常感谢每个人的帮助!!!

答案 6 :(得分:0)

您的阵列是基于0还是基于1?

多次运行这样的循环:

if H.SampleSize = 1 then
begin
  for i:= 1 TO H.NrOfSamples DO
    FStream.Read( DiskTrc1[i], 1);
  Unpack(DiskTrc1);
  ...

你确定这不是读过DiskTrc1的结尾吗?