Both the Tedit and the string are managed types, indeed, but:TEdit is not a managed type, at least not in the sense that a String is. It is bound to the lifetime of it's owner, but it's not actively managed by the compiler, so a const modifier doesn't change anything there.
Huh? It is refcounted?
:o :PHuh? It is refcounted?
Strings are reference-counted, yes.
Without const/constref or var you are actually working with local copies!
Not sure how dynamic arrays fit into this scheme. If you pass one without using "var" and do a SetLength(), are you changing a local copy or are you changing the implicitly referenced original?
After NoVar: 5
After WithVar: 100
By reference parameters in Pascal don't base on pointer syntax like in C (& or *) . You therefore don't need to micro manage it, but use proper by ref language constructs like const/var/out.
constref only makes a const argument forcedly by ref, but this is rarely used because strings and dynamic arrays already contain an implicit ref.
Huh? It is refcounted?Components are not refcounted by default. Yes, through TComponent they implement IInterface, but that is only valid if you access it through the VCLComObject member which is an interface. Class instances themselves are never reference counted by the compiler (only if you access them through an interface type the _AddRef and _Release methods are called at all), thus const vs. non-const does not matter in that regard.
He's saying dynamic arrays (and strings!) contain an implicit ref. Which is a good thing - but your experiment showed "passing by value" behavior.
Very, very basically (it's probably more complex) what the compiler does in the first case ("NoVar") is make a copy of the array or string and pass to the procedure a reference (pointer) to that copy
Not quite correct. Both arrays and strings are in fact pointers to an a bit more involved structure which both contain reference counts. So assigning one to a new variable or parameter normally involves an increase in the reference count (and later on decrease). For normal parameters the reference count is increased when the reference is passed to it, for var, const and constref parameters it is not.He's saying dynamic arrays (and strings!) contain an implicit ref. Which is a good thing - but your experiment showed "passing by value" behavior.
What Marco says is applicable mostly if your code does very low-level operations, say, if you write pure assembler or if you're used to the "C" way of doing things. Very, very basically (it's probably more complex) what the compiler does in the first case ("NoVar") is make a copy of the array or string and pass to the procedure a reference (pointer) to that copy, to avoid pushing the whole array/string into the stack (and other reasons, e.g. to be able to modify it inside the procedure without doing "gymnastics" with it).
The point here is that with var you're treating with a reference to the real variable but without it it's a reference to a "local" copy.
1
Test: 002A6940
2
Test: 002A6960
1
002A6940
1
Test2: 002A6940
1
Test: 002A6940
1
002A6940
2
Test2: 002A6940
2
Test: 002A6960
1
002A6960
002A6940
If -for example- a fixed size array or string is in the parameter list, it is passed by value normally, but passed by reference, when it is const.An array or just about any variable that does not fit in a register will always be passed by reference. The compiler has no choice because the value is simply too large to fit in a register (in some rare cases, some values may be passed or returned in two registers but, again, those are rare cases.)
For contrast: in C fixed size arrays are always passed by reference, no matter if const or not.
An array or just about any variable that does not fit in a register will always be passed by reference. The compiler has no choice because the value is simply too large to fit in a register
"Reference variable" means, there is a hidden pointer passed on the stack. The only passed pointer is the stackpointer in pascal.I'm going to interpret "Reference variable" as a "var" parameter. In that case, a pointer to the variable is what is passed as the parameter to the function/procedure. I'm not sure I'd consider it "hidden" since "var" implies "pointer to", though I admit, it is not obvious to some programmers.
The hidden pointer is in C, but not in Pascal.I don't get what you are saying there. If there is one thing C does not do is hide much of anything, much less pointers. What do you mean "the hidden pointer is in C" ? can you give an example ?
So far I understand it, the data is copied to the stack and the receiving procedure treats the arguments in the same way as an ordinary local variable.For a large data structure (one that does not fit in registers), a copy is made on the stack and a reference/pointer to that copy is used by the callee to access it. It is worth pointing out that in Free Pascal, depending on the program's bitness, the copy may be created by the caller or by the callee, IOW, who makes the copy depends on the program's bitness but, regardless of who made the copy, the structure will always be accessed through a pointer (though this fact may not be obvious to some programmers.)
There is no hidden pointer for an array, that is passed by value in pascal.The array will _always_ be accessed using a pointer. The array identifier is a pointer to the start of the array and an array will _always_ be passed by reference. What changes, when the caller made the copy (if passed by value) is the target of the reference.
(The data must be copied to the stackframe, otherwise the procedure would not be reentrant.)The data is copied if the parameter is passed by value and "const" isn't specified. Whether a function/procedure is re-entrant is a different matter, no language, Free Pascal included, guarantees that a function or procedure is re-entrant, it's the programmer's responsibility to make it that way.
The hidden pointer is in C, but not in Pascal.I don't get what you are saying there. If there is one thing C does not do is hide much of anything, much less pointers. What do you mean "the hidden pointer is in C" ? can you give an example ?
Don't want to put words in his mouth but in C++ (a language different than C) a "reference" (&) is a const pointer .... etcYou are correct. In C++, parameters can be passed by reference just as they are in Pascal. Not so in C. If anything the pointer is "hidden" in Pascal (and C++ when passing by reference) but never in C.
You can also do that in Pascal if you like unreadable code with ambiguous looks
// bare pointers GetStats(x, xx, n, @mean, @stDev); // or GetStats(x, xx, n, Addr(mean), Addr(stDev)); // Or with pointer cast GetStats(x, xx, n, PDouble(@mean), PDouble(@stDev)); // or fully de-referenced GetStats(x, xx, n, PDouble(@mean)^, PDouble(@stDev)^);
"In Free Pascal all non-trivial objects are passed by reference". True. But basically it's just a matter of bandwidth. It's cheaper to send a reference, so references are what get sent. And when you stick "var" in front of it, the called function has read / write access. If you leave the "var" out, then it's just read access. (Let's forget about "const" for the time being)Not quite correct. A normal parameter is read/write just like a var-parameter is, but the caller (not the callee) creates a copy of the value.
Now - at runtime - if the called function tries to modify a "no var" (read only) reference then the called function creates a local copy and uses the local copy for its local purposes. And this local copy disappears and its resources get freed when the called function exits.
So the take away for me is that objects are routinely passed as references. No need to declare (class& object) in the parameters. "Var" in the parameters means "non const". Not sure what "const" means. It's kinda covered by the absence of "var". So it must have some other meaning to the compiler.
As so often that depends on the used calling convention and ABI.So far I understand it, the data is copied to the stack and the receiving procedure treats the arguments in the same way as an ordinary local variable.For a large data structure (one that does not fit in registers), a copy is made on the stack and a reference/pointer to that copy is used by the callee to access it. It is worth pointing out that in Free Pascal, depending on the program's bitness, the copy may be created by the caller or by the callee, IOW, who makes the copy depends on the program's bitness but, regardless of who made the copy, the structure will always be accessed through a pointer (though this fact may not be obvious to some programmers.)
.section .text.n_p$tarrtest_$$_testreg$ttest$$longint,"x"
.balign 16,0x90
.globl P$TARRTEST_$$_TESTREG$TTEST$$LONGINT
P$TARRTEST_$$_TESTREG$TTEST$$LONGINT:
# Temps allocated between ebp-20 and ebp-8
# [tarrtest.pp]
# [66] begin
pushl %ebp
movl %esp,%ebp
leal -20(%esp),%esp
# Var a located at ebp-4, size=OS_32
# Var $result located at ebp-8, size=OS_S32
movl %eax,-4(%ebp)
movl -4(%ebp),%edx
movl (%edx),%eax
movl %eax,-20(%ebp)
movl 4(%edx),%eax
movl %eax,-16(%ebp)
movl 8(%edx),%eax
movl %eax,-12(%ebp)
# [67] TestReg := a.a;
movl -20(%ebp),%eax
movl %eax,-8(%ebp)
# [68] end;
movl -8(%ebp),%eax
movl %ebp,%esp
popl %ebp
ret
.section .text.n_p$tarrtest_$$_teststdcall$ttest$$longint,"x"
.balign 16,0x90
.globl P$TARRTEST_$$_TESTSTDCALL$TTEST$$LONGINT
P$TARRTEST_$$_TESTSTDCALL$TTEST$$LONGINT:
# [71] begin
pushl %ebp
movl %esp,%ebp
leal -4(%esp),%esp
# Var a located at ebp+8, size=OS_NO
# Var $result located at ebp-4, size=OS_S32
# [72] TestStdCall := a.a;
movl 8(%ebp),%eax
movl %eax,-4(%ebp)
# [73] end;
movl -4(%ebp),%eax
movl %ebp,%esp
popl %ebp
ret $12
.section .text.n_p$tarrtest_$$_testcdecl$ttest$$longint,"x"
.balign 16,0x90
.globl P$TARRTEST_$$_TESTCDECL$TTEST$$LONGINT
P$TARRTEST_$$_TESTCDECL$TTEST$$LONGINT:
# [76] begin
pushl %ebp
movl %esp,%ebp
leal -4(%esp),%esp
# Var a located at ebp+8, size=OS_NO
# Var $result located at ebp-4, size=OS_S32
# [77] TestCDecl := a.a;
movl 8(%ebp),%eax
movl %eax,-4(%ebp)
# [78] end;
movl -4(%ebp),%eax
movl %ebp,%esp
popl %ebp
ret
.section .text.n__main,"x"
.balign 16,0x90
.globl _main
_main:
.globl PASCALMAIN
PASCALMAIN:
# [82] begin
pushl %ebp
movl %esp,%ebp
call fpc_initializeunits
# [83] a.a := 42;
movl $42,U_$P$TARRTEST_$$_A
# [84] a.b := 21;
movl $21,U_$P$TARRTEST_$$_A+4
# [85] a.c := 8;
movl $8,U_$P$TARRTEST_$$_A+8
# [86] TestReg(a);
movl $U_$P$TARRTEST_$$_A,%eax
call P$TARRTEST_$$_TESTREG$TTEST$$LONGINT
# [87] TestStdCall(a);
leal -12(%esp),%esp
movl U_$P$TARRTEST_$$_A,%eax
movl %eax,(%esp)
movl U_$P$TARRTEST_$$_A+4,%eax
movl %eax,4(%esp)
movl U_$P$TARRTEST_$$_A+8,%eax
movl %eax,8(%esp)
call P$TARRTEST_$$_TESTSTDCALL$TTEST$$LONGINT
# [88] TestCDecl(a);
leal -12(%esp),%esp
movl U_$P$TARRTEST_$$_A,%eax
movl %eax,(%esp)
movl U_$P$TARRTEST_$$_A+4,%eax
movl %eax,4(%esp)
movl U_$P$TARRTEST_$$_A+8,%eax
movl %eax,8(%esp)
call P$TARRTEST_$$_TESTCDECL$TTEST$$LONGINT
addl $12,%esp
# [90] end.
call fpc_do_exit
movl %ebp,%esp
popl %ebp
ret
"In Free Pascal all non-trivial objects are passed by reference". True. But basically it's just a matter of bandwidth. It's cheaper to send a reference, so references are what get sent. And when you stick "var" in front of it, the called function has read / write access. If you leave the "var" out, then it's just read access. (Let's forget about "const" for the time being)Not quite correct. A normal parameter is read/write just like a var-parameter is, but the caller (not the callee) creates a copy of the value.
Now - at runtime - if the called function tries to modify a "no var" (read only) reference then the called function creates a local copy and uses the local copy for its local purposes. And this local copy disappears and its resources get freed when the called function exits.
So the take away for me is that objects are routinely passed as references. No need to declare (class& object) in the parameters. "Var" in the parameters means "non const". Not sure what "const" means. It's kinda covered by the absence of "var". So it must have some other meaning to the compiler.
As so often that depends on the used calling convention and ABI.Yes, definitely. I should have specified that my comments were applicable to the compiler's default parameter passing convention, namely register.
This behaviour not only depends on the calling convention, but also on the passed type: for example records that contain managed types are always passed as reference.I stay away from managed types as much as I can, as a result, I know very little about how they are treated. I will take your word for how those are handled. Thank you for making that clear.
Sometimes it also depends on the size whether a record is passed as reference or a direct copy (e.g. a copy of a record of size 4 could be passed directly inside a register).That makes perfect sense. if it fits in a register and it is passed by value, it should be passed in a register (if there any still available, of course.) I presume that in the case of a record of size 4, that implies one (1) field of size 4 and, if there were two fields whose combined size was 4 then two (2) registers would be used, correct ?
A normal parameter is read/write just like a var-parameter is, but the caller (not the callee) creates a copy of the value.As far as I know.
Type | No prefix | const | constref | var | out |
Simple | Direct | Direct | Address | Address | Address |
Complex but fit in Simple | Direct | Address | Address | Address | Address |
Complex large | Address | Address | Address | Address | Address |
Pointer or class | Direct | Direct | Address | Address | Address |
Pointer of managed type | Direct | Direct | Address | Address | Address, but first dec ref |
Type | No prefix | const | constref | var | out |
Simple | Nothing | Nothing | Nothing | Nothing | Nothing |
Complex but fit in Simple | Make copy | Nothing | Nothing | Nothing | Nothing |
Complex large | Make copy | Nothing | Nothing | Nothing | Nothing |
Pointer/class | Nothing | Nothing | Nothing | Nothing | Nothing |
Pointer of managed type | Inc/Dec Ref+try/finally | Nothing | Nothing | Nothing | Zeroing original or P^ := nil |
Only few types are reference counted, namely strings and interfaces. So for the majority the general rules apply that I mentioned before. For reference counted types the compiler increases the reference count before passing such a variable to a normal parameter and then passing in the string/interface variable as is. For var-parameters and friends the reference count is not increased before the call."In Free Pascal all non-trivial objects are passed by reference". True. But basically it's just a matter of bandwidth. It's cheaper to send a reference, so references are what get sent. And when you stick "var" in front of it, the called function has read / write access. If you leave the "var" out, then it's just read access. (Let's forget about "const" for the time being)Not quite correct. A normal parameter is read/write just like a var-parameter is, but the caller (not the callee) creates a copy of the value.
Now - at runtime - if the called function tries to modify a "no var" (read only) reference then the called function creates a local copy and uses the local copy for its local purposes. And this local copy disappears and its resources get freed when the called function exits.
So the take away for me is that objects are routinely passed as references. No need to declare (class& object) in the parameters. "Var" in the parameters means "non const". Not sure what "const" means. It's kinda covered by the absence of "var". So it must have some other meaning to the compiler.
Oh boy. I initially agreed with this take. For several hours. Until that other guy mentioned that it was reference counted. Which I'm guessing means that no "deep" copies happen until there is an actual need - until one of the variables becomes different from the shared reference. And I'm assuming that the change that triggers the deep copy (the copy that gets modified) usually occurs "inside" the called function. The caller remains isolated from what's going on in the called function (because of "no var"), and continues to reference the original data. It seems inefficient for the caller to always create a deep copy (and pass a reference to it) when there are often cases in which no deep copy is ever needed.
If the combined size is indeed 4 it will probably be passed in one register. But in the end it depends on the ABI as well.Sometimes it also depends on the size whether a record is passed as reference or a direct copy (e.g. a copy of a record of size 4 could be passed directly inside a register).That makes perfect sense. if it fits in a register and it is passed by value, it should be passed in a register (if there any still available, of course.) I presume that in the case of a record of size 4, that implies one (1) field of size 4 and, if there were two fields whose combined size was 4 then two (2) registers would be used, correct ?