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:

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;
