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;