To go a bit further about what finalizing memory means. There are two kinds of types in Pascal, Managed types (managed records, COM interfaces, strings and arrays) and "simple" types.
Managed types need to be initialized and finalized. This is normally taken care of by the compiler implicetly. Simple types, don't need initialization and finalization. They might need some constructors or destructors (like objects and classes), but this is something different
A variable is located either on the stack, the heap or part of another block/variable (which than itself can be on the stack, heap, or part of another variable that can be on ...). There are also global variables, but let's just pretend they are on the stack for simplicity. The compiler tries to do the initialization and finalization implicetly. So if a new stack frame is entered, all variables on the stack are intialized, and when a stack frame is popped, all variables are finalised
Example:
program Project1;
{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
type
TManagedType = record
Data: Integer;
class operator Initialize(var t: TManagedType);
class operator Finalize(var t: TManagedType);
end;
class operator TManagedType.Initialize(var t: TManagedType);
begin
WriteLn('Initializing TManagedType');
end;
class operator TManagedType.Finalize(var t: TManagedType);
begin
WriteLn('Finalizing TManagedType');
end;
procedure Test;
var
t: TManagedType;
begin
t.Data := 42;
WriteLn('Leaving Function -> Popping stack frame');
end;
begin
WriteLn('Entering Function -> Creating Stack Frame');
Test;
ReadLn;
end.
Result:
Entering Function -> Creating Stack Frame
Initializing TManagedType
Leaving Function -> Popping stack frame
Finalizing TManagedType
So when the function is entered, the stack frame is created, therefore the record variable "t" is initialized.
When the function exits, the stack frame is popped and the record variable "t" is finalised.
This is very easy for the compiler, it is like a hidden "try-finally" block. The second case that is easy for the compiler is the third case, when your variable is part of another variable, then the "child" variable is initialized whenever the parent variable is initialized and finalized when the parent is finalized:
program Project1;
{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
type
TManagedType2 = record
Data: Integer;
class operator Initialize(var t: TManagedType2);
class operator Finalize(var t: TManagedType2);
end;
TManagedType = record
Data: TManagedType2;
class operator Initialize(var t: TManagedType);
class operator Finalize(var t: TManagedType);
end;
class operator TManagedType2.Initialize(var t: TManagedType2);
begin
WriteLn('Initializing TManagedType2');
end;
class operator TManagedType2.Finalize(var t: TManagedType2);
begin
WriteLn('Finalizing TManagedType2');
end;
class operator TManagedType.Initialize(var t: TManagedType);
begin
WriteLn('Initializing TManagedType');
end;
class operator TManagedType.Finalize(var t: TManagedType);
begin
WriteLn('Finalizing TManagedType');
end;
procedure Test;
var
t: TManagedType;
begin
t.Data.Data := 42;
WriteLn('Leaving Function -> Popping stack frame');
end;
begin
WriteLn('Entering Function -> Creating Stack Frame');
Test;
ReadLn;
end.
Entering Function -> Creating Stack Frame
Initializing TManagedType2
Initializing TManagedType
Leaving Function -> Popping stack frame
Finalizing TManagedType
Finalizing TManagedType2
Same holds for classes, on create all "children" are initialized and on free all "children" are finalised:
program Project1;
{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
type
TManagedType = record
Data: Integer;
class operator Initialize(var t: TManagedType);
class operator Finalize(var t: TManagedType);
end;
TTestClass = class
public
Data: TManagedType;
constructor Create;
destructor Destroy; override;
end;
constructor TTestClass.Create;
begin
WriteLn('Creating class');
end;
destructor TTestClass.Destroy;
begin
inherited Destroy;
WriteLn('Destroying class');
end;
class operator TManagedType.Initialize(var t: TManagedType);
begin
WriteLn('Initializing TManagedType');
end;
class operator TManagedType.Finalize(var t: TManagedType);
begin
WriteLn('Finalizing TManagedType');
end;
var
t: TTestClass;
begin
t := TTestClass.Create;
t.Free;
ReadLn;
end.
Yields:
Initializing TManagedType
Creating class
Destroying class
Finalizing TManagedType
This is also the same for arrays, as shown above. If your variable is part of an array, when you increase the number of elements of an array, every new element will be initialised, and when you decrease the number of elements of an array, all elements will be finalised:
program Project1;
{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
type
TManagedType = record
Data: Integer;
class operator Initialize(var t: TManagedType);
class operator Finalize(var t: TManagedType);
end;
class operator TManagedType.Initialize(var t: TManagedType);
begin
WriteLn('Initializing TManagedType');
end;
class operator TManagedType.Finalize(var t: TManagedType);
begin
WriteLn('Finalizing TManagedType');
end;
var
t: Array of TManagedType;
begin
WriteLn('Adding 2 elements');
SetLength(t, 2);
WriteLn('Removing 1 element');
SetLength(t, 1);
WriteLn('Removing all remaining (1) elements');
SetLength(t, 0);
ReadLn;
end.
Adding 2 elements
Initializing TManagedType
Initializing TManagedType
Removing 1 element
Finalizing TManagedType
Removing all remaining (1) elements
Finalizing TManagedType
The juicy bits come when you start dealing with the heap and with anonymous memory regions and pointers, but as I am currently on short time, I will leave that out for another time.