Home > Blockchain >  Reason why a freed TStringList counts as memory leak in the FastMM4 report
Reason why a freed TStringList counts as memory leak in the FastMM4 report

Time:02-18

After noticing an odd memory behavior in task manager in one of my applications I started investigating memory leaks with FastMM4. I found more than 1000 just after opening three forms. I hope I will be able to find the cause in a single place

In one case, even if always free the TStringList in the finally block (that is always reached), FastMM4 reports a memory leak:

function GetMatchingResourceFileName(MatchingString:string) : string;
var
  ResourcesList: TStringList;
  I : Integer;
begin
  Result := '';
  try
    ResourcesList := TStringList.Create;
    // get resource files list
    ResourcesList := GetResourceList;
    //search for matching file name
    for I := 0 to ResourcesList.Count-1 do
    begin
      if Pos(MatchingString,ResourcesList.Strings[I]) > 0 then
      begin
        Result := ResourcesList.Strings[I];
        break;
      end;
    end;
  finally
    ResourcesList.Free;
    ResourcesList:= nil;
  end;
end;

FastMM4 stack report tells me that the leak starts from

ResourcesList := TStringList.Create;

even if I am 100% sure that ResourcesList.Free; is executed I see the memory leak.

Here you can see that the breakpoint is hit: breakpoint being hit

As I close the app I see the report:

---------------------------
myProject.exe: Memory Leak Detected
---------------------------
This application has leaked memory. The small-block leaks are (excluding expected leaks registered by pointer):



85 - 100 bytes: System.Classes.TStringList x 1



Note: Memory leak detail is logged to a text file in the same folder as this application. To disable this memory leak check, undefine "EnableMemoryLeakReporting".

To study the leak above I commented 99% of my code focus on the first reported leak, in fact, one in the initialization part of the app.

How this is possible?

Update

The working version of the code avoids to call TStringList.Create since the GetResourceList method already returns a properly created TStringList, the following code is now leak free:

function GetMatchingResourceFileName(MatchingString:string) : string;
var
  ResourcesList: TStringList;
  I : Integer;
begin
  Result := '';
  try
    ResourcesList := GetResourceList;
    //search for matching file name
    for I := 0 to ResourcesList.Count-1 do
[...]

CodePudding user response:

You have two problems:

1

1.    ResourcesList := TStringList.Create;
2.    // get resource files list
3.    ResourcesList := GetResourceList;

On line 1, you create a new TStringList object and save the address to this in the local ResourcesList variable.

But on line 3, I suppose the GetResourceList function also creates a new TStringList object, and then you rewrite the local ResourcesList variable so that it points to this, new, object instead.

This means that there now is no variable pointing to the first TStringList object you created. Hence, it is forever leaked.

What you want is this:

    // get resource files list
    ResourcesList := GetResourceList;

2

Your code is essentially

try
  ResourcesList := TStringList.Create; //or GetResourceList;
  // Use the list
finally
  ResourcesList.Free
end;

This is a very common bug. If the TStringList.Create constructor fails (or the GetResourceList function), the partially created TStringList object is automatically freed (or hopefully freed by the GetResourceList function), but then the exception stops the execution, so no value is written to ResourcesList.

Hence, ResourcesList.Free will then run the destructor on a random pointer, since local variables of unmanaged types are not initialized.

You must do

ResourcesList := TStringList.Create; //or GetResourceList;
try
  // Use the list
finally
  ResourcesList.Free
end;
  • Related