kseen
kseen

Reputation: 397

Is there memory leak here?

is this piece of code safe from 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 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;

I'm wondering s := GetSomeSettings; potentially harmful and ignoring first object, keeps it in the memory?

Upvotes: 3

Views: 428

Answers (2)

Arioch 'The
Arioch 'The

Reputation: 16045

1) you should not ask when you can check in two minutes!

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) Still that could be safe in few niche cases.

  1. in FPC (http://FreePascal.org) S could be a "global property" of some unit, having a setter which would free old list.
  2. in Delphi Classic S could be of some interface type, supported by the created object. Granted, standard TStringList lacks any interface, but some libraries ( for example http://jcl.sf.net ) do offer interface-based string lists, with richer API (iJclStringList type and related).
  3. in Delphi/LLVM all objects were made reference-counted, like interfaces without GUID's. So that code would be safe there.
  4. You can declare S as a record - a so-called Extended Record having re-defined class operator Implicit so that the typecast s{record} := TStringList.Create would free the previous instance before assigning a new one. That is dangerous though, as it is VERY fragile and easy to misuse, and destroy the list in some other place leaving a dangling pointer inside the S record.
  5. Your object may be not that vanilla TStringList, but some subclass, overriding constructors or AfterConstruction to register itself in some list, that would be all-at-once in some place. Kind of Mark/Sweep heap management around large chunk of workload. VCL TComponent may be seen as following this pattern: form is owning its component and frees them when needed. And this is what you - in reduced form - are trying to do with TSettingsClass.fSettings containter (any reference is 1-sized container). However those frameworks do require a loopback: when the object is freed it should also remove itself from all the containers, referencing it.

.

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;

However then - by the obvious need to keep design symmetric - the registration should also be done as a part of the class. Who is responsible for de-registration is usually the one responsible for registration as well, unless there are reasons to skew the design.

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;

Upvotes: 7

NGLN
NGLN

Reputation: 43649

Yes, the StringList created on line 1 is leaked.

Essentialy, you are doing:

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

As for the GetSomeSettings routine:

Normally it is not wise or encouraged to return newly created instances as function results, because you transfer the responsibility for ownership and destruction to the calling code. Unless you have a mechanism/framework in place that takes care of it, which seems to be the case with your TSettingsClass, but there is not enough evidence for that in this little piece of code.

Nevertheless, the combination of both pieces of code display another problem: After s.Free, TSettingsClass.fSettings is destroyed but not nil. Thus the second time GetSomeSettings is called, it returns a dangling pointer.

Upvotes: 18

Related Questions