Delphi 这里有内存泄漏吗?

Delphi 这里有内存泄漏吗?,delphi,memory,optimization,memory-management,memory-leaks,Delphi,Memory,Optimization,Memory Management,Memory Leaks,这段代码可以防止内存泄漏吗 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

这段代码可以防止内存泄漏吗

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潜在有害并忽略第一个对象,将其保留在内存中?

是的,在第1行创建的StringList泄漏

基本上,你正在做:

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

对于
GetSomeSettings
例程:

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

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

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) 不过,在少数特殊情况下,这可能是安全的

  • 在FPC中()
    S
    可能是某个单元的“全局属性”,具有释放旧列表的setter
  • 在Delphi Classic中,
    S
    可以是某种接口类型,由创建的对象支持。诚然,标准
    TStringList
    缺少任何接口,但一些库(例如)确实提供了基于接口的字符串列表,具有更丰富的API(
    iJclStringList
    类型和相关)
  • 在Delphi/LLVM中,所有对象都进行了引用计数,就像没有GUID的接口一样。这样代码在那里就安全了
  • 您可以将
    S
    声明为记录-所谓的
    Extended record
    重新定义了
    class操作符Implicit
    ,以便typecast
    S{record}:=TStringList.Create
    将在分配新实例之前释放以前的实例。但这是很危险的,因为它非常脆弱,很容易被误用,并在其他地方销毁列表,在
    S
    记录中留下一个悬空的指针
  • 您的对象可能不是普通的
    TStringList
    ,而是一些子类,覆盖构造函数或
    AfterConstruction
    ,将自己注册到某个列表中,这将在某个地方同时完成。围绕大量工作负载的
    Mark/Sweep
    堆管理。VCLTComponent可能被视为遵循以下模式:表单拥有它的组件,并在需要时释放它们。这就是你用
    TSettingsClass.fSettings
    container(任何引用都是1号容器)试图做的事情。然而,这些框架确实需要一个环回:当对象被释放时,它也应该从所有容器中移除自己,引用它

  • 只需将第一块代码替换为s:=GetSomeSetting为什么不检查内存泄漏?您可以使用FastMM,或者如果您的Delphi版本支持,则在应用程序启动时,
    ReportMemoryLeaksOnShutdown:=TrueTStringList
    对象泄漏。答案详尽且信息丰富。至少我学到了一些东西+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;