You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MadExcept报DynamicArray内存泄漏,求解析kbmMemTable相关代码问题

为什么MadExcept会报告Delphi动态数组内存泄漏?

我正在用MadExcept检测Delphi程序的内存泄漏,对以下报告的动态数组泄漏存在疑问:

MadExcept泄漏报告详情

allocation number: 526920
program up time: 21.98 s
type: DynamicArray
address: $211e4d00
size: 28
access rights: read/write
reference counter: 1
item count: 5
item size: 4

main thread ($6d9c):
671a7cc7 madExcept32.dll madExceptDbg           1746 GetMemCallback
00d8765f QX.exe          System                 4818 @ReallocMem
00d904ca QX.exe          System                 4818 DynArraySetLength
00d90631 QX.exe          System                 4818 @DynArraySetLength
01b7d2a3 QX.exe          kbmMemTable           17393 TkbmCustomStreamFormat.DetermineSaveFields
00ec42f5 QX.exe          System.Classes              TStream.SetPosition
01b7d7f6 QX.exe          kbmMemTable           17509 TkbmCustomStreamFormat.BeforeSave
0264fcb9 QX.exe          kbmMemCSVStreamFormat   560 TkbmCustomCSVStreamFormat.BeforeSave
01b75126 QX.exe          kbmMemTable           14331 TkbmCustomMemTable.InternalSaveToStreamViaFormat
01b74e97 QX.exe          kbmMemTable           14272 TkbmCustomMemTable.SaveToFileViaFormat
01b74eb3 QX.exe          kbmMemTable           14274 TkbmCustomMemTable.SaveToFileViaFormat
01b75a7c QX.exe          kbmMemTable           14571 TkbmCustomMemTable.SavePersistent
01b75b80 QX.exe          kbmMemTable           14591 TkbmCustomMemTable.SavePersistent
00d8760c QX.exe          System                 4818 @FreeMem
01b6ba03 QX.exe          kbmMemTable           10523 TkbmCustomMemTable.SetActive
010c94c4 QX.exe          Data.DB                     TDataSet.Close
010b7432 QX.exe          Data.DB                     TField.Destroy
00d8a930 QX.exe          System                 4818 TObject.Free
00ed94be QX.exe          System.Classes              TComponent.DestroyComponents
00ed8f47 QX.exe          System.Classes              TComponent.Destroy
00f6be75 QX.exe          Vcl.Controls                TControl.Destroy
00e3e6b9 QX.exe          madExcept                   InterceptClassDestroy
00ec05df QX.exe          System.Classes              TPersistent.Destroy
00f70c51 QX.exe          Vcl.Controls                TWinControl.Destroy
01060b13 QX.exe          Vcl.Forms                   TScrollingWinControl.Destroy
010620e1 QX.exe          Vcl.Forms                   TCustomForm.Destroy
01062100 QX.exe          Vcl.Forms                   TCustomForm.Destroy
00d8a930 QX.exe          System                 4818 TObject.Free
0274b08b QX.exe          ReportAdmin            4057 CDS_CheckAndImportReportFiles
043b1673 QX.exe          Reports                 184 TReports.LoadReports
044257a1 QX.exe          QX.Startup.Common      1174 StartupCode
0448568b QX.exe          QX                      638 initialization

memory dump: 
211e4d00  01 00 00 00 05 00 00 00 - 00 00 00 00 01 00 00 00  ................
211e4d10  02 00 00 00 03 00 00 00 - 04 00 00 00              ............

对应的代码片段

泄漏指向TkbmCustomStreamFormat.DetermineSaveFields方法中的SetLength(SaveFields,nf);语句:

procedure TkbmCustomStreamFormat.DetermineSaveFields(ADataset:TkbmCustomMemTable);
var
   i:TkbmNativeInt;
   nf:TkbmNativeInt;
begin
     // Setup flags for fields to save.
     with ADataset do
     begin
          nf:=Fieldcount;
          SetLength(SaveFields,nf);

          for i:=0 to nf-1 do
          begin
               // Default dont save this field.
               SaveFields[i]:=-1;

               // If a blob field, only save if specified.
               if (Fields[i].DataType in kbmBlobTypes) then
               begin
                    if not (sfSaveBlobs in sfBlobs) then continue;
                    SaveFields[i]:=i;
               end;

               // Only save fields of specific types.
               case Fields[i].FieldKind of
                    fkData,fkInternalCalc: if sfSaveData in sfData then SaveFields[i]:=i;
                    fkCalculated: if sfSaveCalculated in sfCalculated then SaveFields[i]:=i;
                    fkLookup: if sfSaveLookup in sfLookup then SaveFields[i]:=i;
                    else SaveFields[i]:=-1;
               end;

               // If not to save invisible fields, dont.
               if not (Fields[i].Visible or (sfSaveNonVisible in sfNonVisible)) then SaveFields[i]:=-1;
          end;
     end;
end;

泄漏原因分析

你说的没错,Delphi局部动态数组确实会在超出作用域时自动通过引用计数机制释放,但这里的SaveFields并不是局部变量——它是TkbmCustomStreamFormat类的成员变量。

成员动态数组的生命周期和类实例绑定:

  1. 当你在DetermineSaveFields中调用SetLength(SaveFields,nf)时,会分配内存并增加数组的引用计数
  2. 方法执行完毕后,成员变量SaveFields仍然持有该数组的引用,引用计数不会归零
  3. 如果TkbmCustomStreamFormat实例没有被正确销毁,或者在实例销毁前没有显式清空SaveFields(比如调用SetLength(SaveFields, 0)或赋值为nil),数组的内存就不会被释放
  4. MadExcept报告显示该数组的引用计数为1,正好说明存在一个未被释放的活跃引用,就是SaveFields成员变量

要解决这个问题,需要检查TkbmCustomStreamFormat类的销毁逻辑,确保在Destroy方法中清空SaveFields数组,或者在使用完数组后立即重置它。

内容的提问来源于stack exchange,提问作者Frank Pedro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 03:17:43