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

4

2 回答 2

18

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

本质上,你正在做:

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

至于GetSomeSettings日常:

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

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

于 2013-10-29T05:35:31.687 回答
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可能是某个单元的“全局属性”,有一个可以释放旧列表的设置器。
  2. 在 Delphi ClassicS中,可以是某种接口类型,由创建的对象支持。当然,标准TStringList缺少任何接口,但一些库(例如http://jcl.sf.net)确实提供了基于接口的字符串列表,具有更丰富的 API(iJclStringList类型和相关)。
  3. 在 Delphi/LLVM 中,所有对象都进行了引用计数,就像没有 GUID 的接口一样。所以那个代码在那里是安全的。
  4. 您可以声明S为记录 - 所谓Extended Record的重新定义class operator Implicit,以便类型转换s{record} := TStringList.Create在分配新实例之前释放前一个实例。不过这很危险,因为它非常脆弱且容易被滥用,并且会在其他地方破坏列表,从而在S记录中留下一个悬空的指针。
  5. 您的对象可能不是那个 vanilla 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;
于 2013-10-29T07:41:35.003 回答