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