Reputation: 29
Attached is complete code for a memory leak example I am running into. Can some one please advise me as to how to clean up this memory leak. This code can be compiled if you create a form and drop a button on it and then paste in the below code into the .pas file. Thanks in advance for any help that can be provided.
unit LeakForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type PrintRecord = record
PrintString1,
PrintString2,
PrintString3,
PrintString4,
PrintString5,
PrintString6 : string;
PrintFloat1,
PrintFloat2,
PrintFloat3 : Double;
end;
PrintPointer = ^PrintRecord;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
MyPrintLst : TList;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure ClearTList(Var List : TList);
Var I, Count : Integer;
begin
Count := list.Count - 1;
For I := Count DownTo 0 Do
Dispose(List[I]);
List.Clear;
end;
procedure FreeTList(Var List : TList);
Var I, Count : Integer;
begin
ClearTList(List);
List.Free;
end;
procedure AddToPrintList(PrintList : TList;
Const MyStrings : Array of String;
Const MyDoubles : Array of Double);
var
PrintPtr : PrintPointer;
begin
New(PrintPtr);
IF High(MyStrings) >= 0 Then
PrintPtr^.printString1 := MyStrings[0];
Begin
IF High(MyStrings) >= 1 Then
Begin
PrintPtr^.printString2 := MyStrings[1];
IF High(MyStrings) >= 2 Then
Begin
PrintPtr^.printString3 := MyStrings[2];
IF High(MyStrings) >= 3 Then
Begin
PrintPtr^.printString4 := MyStrings[3];
IF High(MyStrings) >= 4 Then
PrintPtr^.printString5 := MyStrings[4];
Begin
IF High(MyStrings) >= 5 Then
PrintPtr^.printString6 := MyStrings[5];
End; {>=5}
End; {>=4}
End; {>=3}
End; {>=2}
End; {>=1}
IF High(MyDoubles) >= 0 Then
Begin
PrintPtr^.PrintFloat1 := MyDoubles[0];
IF High(MyDoubles) >= 1 Then
Begin
PrintPtr^.PrintFloat2 := MyDoubles[1];
IF High(MyDoubles) >= 2 Then
PrintPtr^.PrintFloat3 := MyDoubles[2];
End;
End;
PrintList.add(PrintPtr);
end;
procedure TForm1.Button1Click(Sender: TObject);
Var EstReading : LongInt;
begin
EstReading := 0;
ClearTList(MyPrintLst);
AddToPrintList(MyPrintLst, ['Field1 Data','Field2 Data','Field3 Data','Field4 Data'],
[1,2,3,4]);
AddToPrintList(MyPrintLst, ['Field1 Data','Field2 Data','Field3 Data','Field4 Data'],
[5,6,7,8]);
AddToPrintList(MyPrintLst, ['Field1 Data','Field2 Data','Field3 Data','Field4 Data'],
[9,0,1,2]);
AddToPrintList(MyPrintLst, ['Field1 Data','Field2 Data','Field3 Data','Field4 Data'],
[3,4,5,6]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyPrintLst := TList.Create;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeTList(MyPrintLst);
end;
end.
Upvotes: 1
Views: 1045
Reputation: 613461
When you dispose each item, the runtime needs to know the type of the record. Because you are using a TList
then each item is an untyped pointer. Therefore you need to cast the pointers to the item type so that the runtime knows the type, and knows how to dispose of the item.
Replace
Dispose(List[I]);
with
Dispose(PrintPointer(List[I]));
It's also a little odd that you pass the list as a var parameter and do not modify the reference. And the loop is quite odd too, running backwards for no reason, and the loop bounds are handled in a strange manner. I'd have these functions like this:
procedure ClearTList(List: TList);
Var
I: Integer;
begin
For I := 0 to List.Count - 1 Do
Dispose(PrintPointer(List[I]));
List.Clear;
end;
procedure FreeTList(List: TList);
begin
ClearTList(List);
List.Free;
end;
A more conventional naming convention would be:
type
TPrintRecord = record
....
end;
PPrintRecord = ^TPrintRecord;
The form's OnClose
event can be called multiple times if the form has the caHide
action when closing. The correct event to pair with OnCreate
is OnDestroy
.
The complexity of the logic in AddToPrintList
makes me believe that the data types can be designed in a better way. Arrays suggest themselves instead of individual numbered fields.
Without changing the types, you should at least avoid all that indentation, like this:
procedure AddToPrintList(PrintList: TList; const MyStrings: array of String;
const MyDoubles: array of Double);
var
I: Integer;
Item: PPrintRecord;
Str: string;
Value: Double;
begin
New(Item);
PrintList.Add(Item);
for I := 1 to Min(Length(MyStrings), 6) do
begin
Str := MyStrings[I - 1];
case I of
1:
Item.PrintString1 := Str;
2:
Item.PrintString2 := Str;
3:
Item.PrintString3 := Str;
4:
Item.PrintString4 := Str;
5:
Item.PrintString5 := Str;
6:
Item.PrintString6 := Str;
end;
end;
for I := 1 to Min(Length(MyDoubles), 3) do
begin
Value := MyDoubles[I - 1];
case I of
1:
Item.PrintFloat1 := Value;
2:
Item.PrintFloat2 := Value;
3:
Item.PrintFloat3 := Value;
end;
end;
end;
Upvotes: 5