代码之家  ›  专栏  ›  技术社区  ›  user2703897

txldocument.active:=false导致fastmm4错误消息“fastmm检测到块在释放后已被修改”

  •  2
  • user2703897  · 技术社区  · 6 年前

    在fulldebugmode中使用delphi xe8和fastmm4(版本4.992)有一个奇怪的效果。

    为了重现效果,只需创建一个新的TForm应用程序,将FastMM4放在DPR文件的第一行, 在窗体上放置一个按钮,并在ClickHandler中放置以下代码:

    (您需要安装FastMM 4,必须在FastMM4Options.inc文件和 FullDebugMode.dll必须位于程序的输出文件夹中!)

    procedure TForm3.Button4Click(Sender: TObject);
    var
        dm: tdatamodule;
        doc: txmldocument;
    begin
        //this causes the messagebox to be shown directly after clicking the button. 
        //without it the box is shown when the program is exited.
        FastMM4.FullDebugModeScanMemoryPoolBeforeEveryOperation := true;        
    
        //prepare a xml document
        dm := tdatamodule.create(nil);
        doc := txmldocument.create(dm);
        doc.LoadFromXml('<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?><root/>');
    
        //doc.Active := true;   //no need to set active to true. doc is already active at this point!
    
        //the following does matter. ChildNodes needs to be accessed twice!
        //doc.DocumentElement.ChildNodes.FindNode('first');
        //doc.DocumentElement.ChildNodes.FindNode('second');
    
        //alternative access:
        doc.DocumentElement.ChildNodes.count;                                       //first access to ChildNodes
        doc.DocumentElement.ChildNodes.count;                                       //second access to ChildNodes
    
        //this does matter. if doc stays active then there is no problem!
        doc.Active := false;                                                        //active needs to be set to false!
    
        //doc.free; //doesn't matter if doc is freed manually. doc will be freed when datamodule is freed. problem occurs either way.
        dm.free;
    end;
    

    单击按钮时,FastMM4将显示一个大消息框,其中包含以下报告:

    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): 80(1)
    
    The previous block size was: 228
    
    This block was previously allocated by thread 0xC74, and the stack trace (return addresses) at the time was:
    406C46 [System.pas][System][@GetMem$qqri][4565]
    407A73 [System.pas][System][TObject.NewInstance$qqrv][15975]
    5DF25D [Xml.XMLDoc.pas][Xml.XMLDoc][Xmldoc.TXMLDocument.NewInstance$qqrv][2368]
    408202 [System.pas][System][@ClassCreate$qqrpvzc][17290]
    5DF116 [Xml.XMLDoc.pas][Xml.XMLDoc][Xmldoc.TXMLDocument.$bctr$qqrp25System.Classes.TComponent][2344]
    5E2094 [Unit3.pas][Unit3][TForm3.Button4Click$qqrp14System.TObject][47]
    407E6F [System.pas][System][@IsClass$qqrxp14System.TObjectp17System.TMetaClass][16465]
    51EB39 [Vcl.Controls.pas][Vcl.Controls][Controls.TControl.Click$qqrv][7361]
    535CF3 [Vcl.StdCtrls.pas][Vcl.StdCtrls][Stdctrls.TCustomButton.Click$qqrv][5327]
    536801 [Vcl.StdCtrls.pas][Vcl.StdCtrls][Stdctrls.TCustomButton.CNCommand$qqrr26Winapi.Messages.TWMCommand][5788]
    51E5C8 [Vcl.Controls.pas][Vcl.Controls][Controls.TControl.WndProc$qqrr24Winapi.Messages.TMessage][7245]
    
    The block was previously used for an object of class: TXMLDocument
    
    The allocation number was: 650
    
    The block was previously freed by thread 0xC74, and the stack trace (return addresses) at the time was:
    406C62 [System.pas][System][@FreeMem$qqrpv][4613]
    407A91 [System.pas][System][TObject.FreeInstance$qqrv][15984]
    40824D [System.pas][System][@ClassDestroy$qqrxp14System.TObject][17333]
    5DF241 [Xml.XMLDoc.pas][Xml.XMLDoc][Xmldoc.TXMLDocument.$bdtr$qqrv][2363]
    4C3661 [System.Classes.pas][System.Classes][Classes.TComponent.DestroyComponents$qqrv][15648]
    4C3100 [System.Classes.pas][System.Classes][Classes.TComponent.$bdtr$qqrv][15445]
    4C5543 [System.Classes.pas][System.Classes][Classes.TDataModule.$bdtr$qqrv][16694]
    407B97 [System.pas][System][TObject.Free$qqrv][16052]
    5E20F2 [Unit3.pas][Unit3][TForm3.Button4Click$qqrp14System.TObject][63]
    51EB39 [Vcl.Controls.pas][Vcl.Controls][Controls.TControl.Click$qqrv][7361]
    535CF3 [Vcl.StdCtrls.pas][Vcl.StdCtrls][Stdctrls.TCustomButton.Click$qqrv][5327]
    
    The current thread ID is 0xC74, and the stack trace (return addresses) leading to this error is:
    416526 [fastmm4.pas][FastMM4][InternalScanMemoryPool$qqruiui][10018]
    416601 [fastmm4.pas][FastMM4][ScanMemoryPoolForCorruptions$qqrv][10092]
    4161DF [fastmm4.pas][FastMM4][DebugFreeMem$qqrpv][9761]
    406C62 [System.pas][System][@FreeMem$qqrpv][4613]
    407A91 [System.pas][System][TObject.FreeInstance$qqrv][15984]
    40824D [System.pas][System][@ClassDestroy$qqrxp14System.TObject][17333]
    407B8A [System.pas][System][TObject.$bdtr$qqrv][16044]
    40D5DD [System.pas][System][TInterfacedObject._Release$qqsv][37311]
    40D4B7 [System.pas][System][@IntfClear$qqrr44System.%DelphiInterface$17System.IInterface%][36327]
    40B189 [System.pas][System][@FinalizeArray$qqrpvt1ui][31704]
    40B079 [System.pas][System][@FinalizeRecord$qqrpvt1][31407]
    
    Current memory dump of 256 bytes starting at pointer address 7EA18B60:
    98 72 5F 00 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 7F 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
    80 80 80 80 BD 4A 52 7A 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 00 00 00 00 00 00 00 00
    ˜  r  _  .  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
    €  €  €  €  ½  J  R  z  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  .  .  .  .  .  .  .  .
    

    如果我们正确地解释了这一点,那么FastMM告诉我们,TXMLDocument对象的内存 已经被修改,在它被释放之后。

    “一些代码”似乎在Txmldocument的memoryBlock中的80美元中间写下了7F美元 已经被释放了。

    只有在两次访问childnodes(!)时才会发生这种情况。如果txmldocument的active属性设置为false 在释放对象之前!

    问题:

    有人能解释一下这是怎么回事吗?

    • 将TXMLDocument.Active设置为false通常被认为是错误的还是“错误的”(是否已知会导致问题?)

    • 我们是不是犯了别的错误?

    • 这是fastmm4的问题吗?

    • 这是TXMLDocument中的问题吗?

    附加观察:

    如果没有将active设置为false就释放了txmldocument,那么就没有问题。

    如果我们看看Txmldocument的析构函数,我们会发现在 active设置为false:

    destructor TXMLDocument.Destroy;
    begin
      Destroying;
      if FOwnerIsComponent and Active and Assigned(FDocumentNode) and (FRefCount > 1) then      //additional code
        (FDocumentNode as IXMLNodeAccess).ClearDocumentRef;                                     //additional code
      SetActive(False);
      FreeAndNil(FXMLStrings);
      inherited;
    end;
    

    现在,如果我们修改自己的示例代码并调用

    (FDocumentNode as IXMLNodeAccess).ClearDocumentRef; 
    

    在将active设置为false之前,问题已经解决了!

    代码如下所示:

    type
      TMyXMLDocument = class(TXMLDocument);
    
    procedure TForm3.Button4Click(Sender: TObject);
    var
        dm: tdatamodule;
        doc: txmldocument;
    begin
        FastMM4.FullDebugModeScanMemoryPoolBeforeEveryOperation := true;
    
        dm := tdatamodule.create(nil);
        doc := txmldocument.create(dm);
        doc.LoadFromXml('<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?><root/>');
    
        doc.DocumentElement.ChildNodes.count;
        doc.DocumentElement.ChildNodes.count;
    
        (TMyXMLDocument(doc).GetDocumentElement as IXMLNodeAccess).ClearDocumentRef;  //<-- with this  additional hack the problem is gone!
    
        doc.Active := false;                                                        //no more problem!
    
        dm.free;
    end;
    
    1 回复  |  直到 6 年前
        1
  •  5
  •   Stefan Glienke    6 年前

    当调用返回接口的方法/函数时,编译器隐式地生成变量以将这些结果放入它们一直持续到方法结束,然后被最终确定/清除。

    对你来说 doc.DocumentElement.ChildNodes 执行2个返回接口的方法调用现在当你摧毁 TXMLDocument 实例这些隐式变量仍然指向一些内存,这是由于编译器生成代码调用时正在进行的释放调用 IntfClear 他们调用了一些不再有对象的方法-fastmm能够跟踪和报告它。

    因此,上述调用被转换为:

    var
      ...
      nodes1: IXMLNodeList;
      node1: IXMLNode;
      nodes2: IXMLNodeList;
      node2: IXMLNode;
    begin
      ...
    
      node1 := doc.DocumentElement;
      nodes1 := node1.ChildNodes;
      nodes1.count;
      node2 := doc.DocumentElement;
      nodes2 := node2.ChildNodes;
      nodes2.count;
    
      dm.free;
    
      nodes1 := nil;
      node1 := nil;
      nodes2 := nil;
      node2 := nil; // <- boom
    

    在许多情况下,如果不使用FastMM,这个错误就不会出现,除非以前释放的内存没有被重新用于另一个分配,这将导致奇怪的AVs。

    经验法则:不要销毁某些接口在同一范围内引用的实例,因为这可能会导致隐式创建的接口变量仍然指向这些实例。