【问题标题】:Is there memory leak here?这里有内存泄漏吗?
【发布时间】:2013-10-29 04:38:47
【问题描述】:

这段代码对内存泄漏安全吗?

s := TStringList.Create; // create  first object
try
  // Here line comes that seems to be dangerous
  s := GetSomeSettings; // Overrides reference to first object by second one
finally
  s.free; // Destroying only second object, leave first object to live somewhere in memory
end;


function GetSomeSettings : TStringList;
var
  rawString : string;
  settings : TStringList;
begin
  // Singleton pattern implementation

  // Trying to find already existing settings in class variable
  settings := TSettingsClass.fSettings;

  // If there is no already defined settings then get them
  if not Assigned(settings) then
  begin         
    GetSettingsInDB(rawString);
    TSettingsClass.fSettings := ParseSettingsString(rawString);
    settings := TSettingsClass.fSettings;       
  end;
  Result := settings;
end;

我想知道s := GetSomeSettings; 可能有害并忽略第一个对象,将其保留在内存中?

【问题讨论】:

  • 只需将第一段代码替换为 s := GetSomeSettings
  • 为什么不检查内存泄漏呢?您可以使用 FastMM,或者如果您的 Delphi 版本支持它,在应用程序启动时,ReportMemoryLeaksOnShutdown := True; 并且当您的应用程序关闭时您将看到任何内存泄漏。正如其他人指出的那样,这将报告 x1 TStringList 对象泄漏。

标签: delphi memory optimization memory-management memory-leaks


【解决方案1】:

是的,第 1 行创建的 StringList 泄露了。

基本上,你在做:

s := TStringList.Create;
s := AnotherStringList;
AnotherStringList.Free;

至于GetSomeSettings 例程:

通常不明智或不鼓励将新创建的实例作为函数结果返回,因为您将所有权和销毁的责任转移给调用代码。除非你有一个机制/框架来处理它,你的TSettingsClass 似乎就是这种情况,但在这段代码中没有足够的证据。

然而,这两段代码的组合显示了另一个问题:s.Free 之后,TSettingsClass.fSettings 被销毁但不是 nil。因此第二次调用GetSomeSettings,它返回一个悬空指针。

【讨论】:

    【解决方案2】:

    1) 你不应该问什么时候可以在两分钟内检查!

    program {$AppType Console};
    uses Classes, SysUtils;
    
    type TCheckedSL = class(TStringList)
         public
           procedure BeforeDestruction; override;
           procedure AfterConstruction; override;
    end;
    
    procedure TCheckedSL.BeforeDestruction; 
    begin
      inherited;
      WriteLn('List ',IntToHex(Self,8), ' going to be safely destroyed.');
    end;
    
    procedure TCheckedSL.AfterConstruction; 
    begin
      WriteLn('List ',IntToHex(Self,8), ' was created - check whether it is has matched  destruction.');
      inherited;
    end;
    
    procedure DoTest; var s: TStrings;
     function GetSomeSettings: TStrings;
     begin Result := TCheckedSL.Create end;
    begin
      Writeln('Entered DoTest procedure');
      s := TCheckedSL.Create; // create  first object
      try
        // Here line comes that seems to be dangerous
        s := GetSomeSettings; // Overrides reference to first object by second one
      finally
        s.free; // Destroying only second object, leave first object  
      end;
      Writeln('Leaving DoTest procedure');
    end;
    
    BEGIN 
      DoTest;
      Writeln;
      Writeln('Check output and press Enter when done');
      ReadLn;
    END.
    

    2) 在少数特殊情况下,这仍然是安全的。

    1. 在 FPC 中 (http://FreePascal.org) S 可能是某个单元的“全局属性”,有一个可以释放旧列表的设置器。
    2. 在 Delphi Classic 中 S 可能是某种接口类型,由创建的对象支持。当然,标准的 TStringList 缺少任何接口,但一些库(例如 http://jcl.sf.net )确实提供了基于接口的字符串列表,具有更丰富的 API(iJclStringList 类型和相关)。
    3. 在 Delphi/LLVM 中,所有对象都进行了引用计数,就像没有 GUID 的接口一样。这样代码在那里就安全了。
    4. 您可以将S 声明为记录- 所谓的Extended Record 已重新定义class operator Implicit,以便类型转换s{record} := TStringList.Create 在分配新实例之前释放先前的实例。不过这很危险,因为它非常脆弱且容易被滥用,并且会在其他地方破坏列表,在 S 记录中留下一个悬空指针。
    5. 您的对象可能不是那种普通的TStringList,而是一些子类、覆盖构造函数或AfterConstruction 在某个列表中注册自己,这将在某个地方一次性完成。一种围绕大量工作负载的Mark/Sweep 堆管理。 VCL TComponent 可以被视为遵循这种模式:表单拥有它的组件并在需要时释放它们。这就是您 - 以简化的形式 - 正在尝试使用 TSettingsClass.fSettings 容器(任何引用都是 1 大小的容器)。然而,这些框架确实需要一个环回:当对象被释放时,它也应该从所有容器中移除自己,引用它。

    .

    procedure TCheckedSL.BeforeDestruction; 
    begin
      if Self = TSettingsClass.fSettings then TSettingsClass.fSettings := nil;
      inherited;
    end;
    
    class procedure TSettingsClass.SetFSettings(Value);
    var fSet2: TObject;
    begin
      if fSettings <> nil then begin
         fSet2 := fSettings; 
         f_fSettings := nil; // breaking the loop-chain
         fSet2.Destroy; 
      end;
      f_fSettings := Value;
    end;
    
    class destructor TSettingsClass.Destroy;
    begin
      fSettings := nil;
    end;
    

    然而——显然需要保持设计对称——注册也应该作为课程的一部分来完成。除非有理由歪曲设计,否则谁负责注销通常也是负责注册的人。

    procedure TCheckedSL.AfterConstruction; 
    begin
      inherited;
      TSettingsClass.fSettings := Self;
    end;
    
    ...
    if not Assigned(settings) then
      begin         
        GetSettingsInDB(rawString);
        TCheckedSL.Create.Text := ParseSettingsString(rawString);
        settings := TSettingsClass.fSettings;       
        Assert( Assigned(settings), 'wrong class used for DB settings' );
      end;
      Result := settings;
    

    【讨论】:

    • 详尽且内容丰富的答案。至少我学到了一些东西。 +1
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 2013-08-12
    相关资源
    最近更新 更多