The Riddle
This interesting post caused quite the confusion in a Delphi forum:
Enter the Debugger
With the const modifier the memory addresses are the same (pass by reference):
p1: TPoint $427CEC
p2: TPoint $427CEC
Without const, they are different (pass by value):
p1: TPoint $427CEC
p2: TPoint $19FF28
The global variable p1 has a value of 1,1.
When we use the const modifier, p2 references the same value as p1. When the value of p1 changes to 2,2, p2 is still referencing it, hence 2,2 is returned.
Without the const modifier, p2 references a copy of p1's value (1,1). p2's copy is not modified in the method, so it returns 1,1.
Everything is behaving correctly, everything is as expected.
Here be Dragons
It's worth noting that here was a change introduced in Delphi Rio.
This is potentially very important for teams upgrading older Delphi codebases.
With or without const, records sized 1, 2, or 4 bytes are passed as 8-bit, 16-bit, and 32bit values.
They are passed by value always.
TPoint is a record with two fields (2 x LongInt), that's 8 bytes in length.
It is passed by reference when we use the const modifier.
However, if the example code had used TSmallPoint instead of TPoint, it is only 4 bytes in length, and therefore passed by value. With or without the const modifier, the above result would always be 1,1.
To force the TSmallPoint argument to be passed by reference, use the [ref] attribute:
function Test(const [ref] p2: TSmallPoint): TSmallPoint;
This is officially documented here.