这里是否存在内存泄漏?

3
这段代码是否安全,不会出现内存泄漏?
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
只需将第一个代码块替换为 s := GetSomeSettings。 - David Heffernan
1
为什么不加入内存泄漏检查呢?你可以使用FastMM,或者如果你的Delphi版本支持,在应用程序启动时,设置ReportMemoryLeaksOnShutdown := True;,这样当你的应用程序关闭时就会显示任何内存泄漏。正如其他人指出的那样,这将报告一个泄漏的TStringList对象。 - Jerry Dodge
2个回答

18

是的,第1行创建的StringList存在内存泄漏问题。

实质上,您正在执行以下操作:

s := TStringList.Create;
s := AnotherStringList;
AnotherStringList.Free;
关于 GetSomeSettings 这个例程:
通常不明智也不被鼓励将新创建的实例作为函数结果返回,因为这将转移所有权和销毁责任到调用代码。除非你有一个负责处理它的机制/框架,而这似乎是你的 TSettingsClass 的情况,但在这个小片段中没有足够的证据。
尽管如此,这两个代码片段的组合展示了另一个问题:在 s.Free 之后,TSettingsClass.fSettings 被销毁但不是空指针。因此第二次调用 GetSomeSettings时,它会返回一个悬挂指针。

7

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 可以是某个单元的“全局属性”,具有一个 setter,可以释放旧列表。
  2. 在 Delphi Classic 中,S 可以是某个接口类型,由创建的对象支持。尽管标准的 TStringList 没有任何接口,但一些库(例如 http://jcl.sf.net)提供了基于接口的字符串列表,具有更丰富的 API(iJclStringList 类型及其相关内容)。
  3. 在 Delphi/LLVM 中,所有对象都被做成了类似于没有 GUID 的接口的引用计数对象。因此,在那里的代码是安全的。
  4. 您可以将 S 声明为记录 - 所谓的 扩展记录,重新定义 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 - mg30rg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接