SizeOf() behavior with procedural variable in {$MODE DELPHI} differs from Delphi

Summary

In Delphi, performing SizeOf() on a procedural variable name results in the size of the return value of the corresponding procedural type. This differs from the original Turbo/Borland Pascal intention, where this implied the size of a variable itself.

Free Pascal adopts the second behavior. The problem is that Mode Delphi doesn't replace it with the authentic one. I suspect the same trouble (referencing a variable instead of a function) may occur in other cases as well - say, other compile-time intrinsics or type casts.

Possible fixes

It's not clear to me whether this should also be fixed in standard FPC/ObjFPC modes with {$MODESWITCH CLASSICPROCVARS+} enabled.

System Information

  • Operating system: Windows
  • Processor architecture: x86
  • Compiler version: 3.2.2
  • Device: Computer

Example Project

{.$define DELPHIMODE}
{.$define TP_OBJECT}
program sizeof_procvar;

{$IFDEF FPC}
  (* NB: compile this for 32-bit target ('fpc -P i386') *)
  {$IFNDEF DELPHIMODE}
    {$MODE OBJFPC}
    {$MODESWITCH CLASSICPROCVARS ON}
  {$ELSE}{$IFNDEF TP_OBJECT}
    {$MODE DELPHI}
  {$ELSE}
    {$MODE TP}
  {$ENDIF}{$ENDIF}
{$ELSE}{$IFDEF VER70}
  (* for testing on Borland Pascal 7.01 with Objects *)
  {$DEFINE VANILLA}
  {$F+}  (* avoid Error 143 - http://pascal.net.ru/Invalid+procedure+or+function+reference *)
  {$X+,T+}
  {$DEFINE TP_OBJECT}
  {$DEFINE MSWINDOWS}  (* for ReadLn below *)
{$ELSE}
  (* for testing on Delphi 2010 (and maybe newer) *)
  {$APPTYPE CONSOLE}  (* required, otherwise results in Runtime Error 215 *)
{$ENDIF}{$ENDIF}

type
  (* use Real in TP to avoid any possible treatment of it as a 32-bit register.
    otherwise use Byte as it's clearly distinct from any integer or pointer. *)
  sizetype = {$IFNDEF VANILLA} Byte {$ELSE} Real {$ENDIF};  (* Byte=1, Real=6 *)

  TObjeBOSS = {$IFNDEF TP_OBJECT} class {$ELSE} object {$ENDIF}
    function objeboss: sizetype; virtual;
    constructor Init;
  end;

  method_objeboss = {$IFNDEF VANILLA}
    function: sizetype of object
  {$ELSE}
    (* see "Method activations", p.40 in Turbo Pascal 7.0 Language Guide *)
    function(var Self: TObjeBOSS): sizetype
  {$ENDIF};

  func_noparam = function: sizetype;

(* just to init the VMT *)
constructor TObjeBOSS.Init;
begin
{$IFNDEF VANILLA}  (* avoid 'Error 119: No inherited methods are accessible here' *)
  inherited;
{$ENDIF}
end;

function TObjeBOSS.objeboss: sizetype;
begin
  objeboss := 42;
end;

var
  ob: TObjeBOSS;
  mf: method_objeboss;
  fp: func_noparam;
begin
  {$IFNDEF TP_OBJECT} ob := TObjeBOSS.Create {$ELSE} ob.Init {$ENDIF};

{$IFNDEF VANILLA}
  mf := ob.objeboss;
{$ELSE}
  (* see "Variable typecasts" (p.58) and "Type compatibility" (p.46) ibid. *)
  @mf := @TObjeBOSS.objeboss;
{$ENDIF}

  (* FPC, default:           'Sz(mf)=8 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1'
   * FPC, CLASSICPROCVARS+:  'Sz(mf)=8 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1'
   * FPC, {$MODE DELPHI}:    'Sz(mf)=8 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1'
   * FPC, {$MODE TP}:        'Sz(mf)=8 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1'
   * DCC32 (Delphi 2010):    'Sz(mf)=1 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1'
   * Borland Pascal 7.1:     'Sz(mf)=4 Sz(@mf)=4 Sz(fp)=4 Sz(@fp)=4 Sz(func_noparam)=4'
   *)

  WriteLn(
    'Sz(mf)=', SizeOf(mf),
    ' Sz(@mf)=', SizeOf(@mf),
  {$IFNDEF VANILLA}
    ' Sz(mf())=', SizeOf(mf()),  (* BP: 'Error 20: Variable identifier expected' *)
    ' Sz(ob.objeboss)=', SizeOf(ob.objeboss)  (* BP: 'Error 122: Invalid variable reference' *)
  {$ELSE}
    (* https://turbopascal.org/system-function-sizeof *)
    ' Sz(fp)=', SizeOf(fp),  (* variable value - not a function result *)
    ' Sz(@fp)=', SizeOf(@fp),  (* variable address - not its value *)
    ' Sz(func_noparam)=', SizeOf(func_noparam)  (* far call pointer *)
  {$ENDIF}
  );

  {$IFDEF MSWINDOWS} ReadLn; {$ENDIF}
end.

What is the current bug behavior?

With {$define DELPHIMODE} and a 32-bit target, this prints Sz(mf)=8 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1.

What is the expected (correct) behavior?

Sz(mf)=1 Sz(@mf)=4 Sz(mf())=1 Sz(ob.objeboss)=1
Edited by Dmitry D. Chernov