10.5 A note on scope and lifetime for record and type helpers

For classes, the lifetime of an instance of a class is explicitly managed by the programmer. It is therefore clear what the Self parameter means and when it is valid.

Records and other simple types are allocated on the stack, which means they go out of scope when the function, procedure or method in which they are used, exits.

Combined with the fact that helper methods are type compatible to class methods, and can therefore be used as event handlers, this can lead to surprising situations: The data pointer in a helper method is set to the address of the variable.

Consider the following example:

{$mode objfpc}  
{$modeswitch typehelpers}  
uses  
  Classes;  
 
type  
  TInt32Helper = type helper for Int32  
    procedure Foo(Sender: TObject);  
  end;  
 
procedure TInt32Helper.Foo(Sender: TObject);  
begin  
  Writeln(Self);  
end;  
 
var  
  i: Int32 = 10;  
  m: TNotifyEvent;  
begin  
  m := @i.Foo;  
  WriteLn(’Data : ’,PtrUInt(TMethod(m).Data));  
  m(nil);  
end.

This will print something like (the actual value for data may differ):

Data : 6848896  
10

The variable i is still in scope when m is called.

But changing the code to

{$mode objfpc}  
{$modeswitch typehelpers}  
uses  
  Classes;  
 
type  
  TInt32Helper = type helper for Int32  
    procedure Foo(Sender: TObject);  
  end;  
 
procedure TInt32Helper.Foo(Sender: TObject);  
begin  
  Writeln(Self);  
end;  
 
Function GetHandler  :TNotifyEvent;  
 
var  
  i: Int32 = 10;  
 
begin  
  Result:=@i.foo;  
end;  
 
Var  
  m: TNotifyEvent;  
begin  
  m := GetHandler;  
  WriteLn(PtrUInt(TMethod(m).Data));  
  m(nil);  
end.

The output will be:

140727246638796  
0

The actual output will depend on the architecture, but the point is that i is no longer in scope, making the output of its value meaningless, and possibly even leading to access violations and program crashes.