Different function parameter modifiers in Delphi - Ali Keshavarz
(Useful article that was originally posted on now defunct vcldeveloper.com)
Recently a friend asked me about constant parameters in Delphi
functions, that brought the idea of writing a new post about passing
parameters to functions/procedures in Delphi.
Parameter modifiers
When you call a function or procedure (I am going to refer to both of them as Functions here), you have to somehow pass the required input or output parameters to it. Parameters are passed to functions either by value, or by reference.Passing by value means, compiler makes a new copy of the original data, and sends it to the function. This way, the function has its own copy of data, and changing value of the parameter inside the function does not affect the original data. This is the default mode when you pass a parameter to a function in Delphi:
program Test02; procedure Foo(Param1: Integer ); begin Inc(Param1); //=> Has no effect on MyData. end ; var MyData : Integer ; begin MyData := 1 ; Foo(MyData); Writeln (MyData); //=> MyData is still 1. end . |
program Test02; procedure Foo( Var Param1: Integer ); begin Inc(Param1); end ; var MyData : Integer ; begin MyData := 1 ; Foo(MyData); Writeln (MyData); //=> MyData is 2. end . |
“Const” makes the parameter read-only, so that you cannot change its value inside the function. This lets compiler to generate optimized code when there is no need to alter value of that parameter inside the function.
procedure Foo( const Param1: Integer ); begin Inc(Param1); // ERROR! You cannot modify a const parameter. end ; |
“Out” is similar to “var”, the difference is, when you use “Out” the initial value of the parameter is discarded inside the function, and it does not matter. What matters is the value you assign to that parameter inside your function. “Out” is basically there to support MS COM method declaration in Delphi. You can use it in any function you write, but you will see lots of COM methods having Out parameters in their declaration.
OK, the purpose of Const and Out modifiers are clear, it can be guessed even from their name which represent Constant and Output; but, to which category do they belong?
Are “Out” and “Const” passed by value or by reference?
For “out”, as I mentioned above, it acts similar to “var”, so it passes parameters by reference.How about “Const’? Const acts somehow differently! It passes parameters usually by value, but it passes certain data types by reference too! Which data types are passed by value, and which ones are passed by reference? To clarify this, I wrote a simple program; it shows address of a parameter passed to a function using the default passing (by value), var modifier (by reference), and const modifier. It repeats this for various data types, to show how these modifiers pass different data types to a function. The source code is attached at the end of this post. Here is a screenshot of this sample program:
For each data type, “Original:” line indicates address of the original data before being passed to the function. “Value:” line indicates address of the parameter passed to the function by value. “Const:” line indicates address of the parameter passed to the function as a constant. And finally, “var:” line indicates address of the parameter passed to the function by reference.
As you can see, for all data types, address of var parameter is the same as address of the original data, and address of value parameter (passed by value) differs from the original data. Therefore, we can conclude passing a parameter by value, always makes a copy of the original data; and passing a parameter with var modifier always refers to the original data, regardless of the data type which is used.
But for constant parameters, we can see that it always passes the parameter by value, except when the parameter is of array type (static arrays and open arrays, not dynamic arrays; dynamic arrays are passed similar to objects), or record type. It means, if you do not specify a modifier for a parameter of type array (not dynamic arrays) or records, then every time your function is called, the whole data inside that array or record will be pushed into stack, and when the execution of your function is over, it is dismissed! On the other hand, when you use Const or Var modifiers, only a pointer to your record or array is passed to the function, so there is no need to copy the whole data of the array or record.
Now you might ask, if the compiler is smart enough to optimize array and record parameters when Var or Const modifiers are used, then why doesn’t it use the same optimization for objects, interfaces, strings, or dynamic arrays?! The answer is: Because those are special data types!
Objects, Interfaces, Strings, and Dynamic Arrays as parameters
Objects, interfaces, strings, and dynamic arrays are behind the scene just pointers. They do not contain the real data. They are pointers that refer to the real data. That’s why if you call SizeOf() function on a string or object type, even if your string or object contains huge data; the result is always 4 (as long as Delphi compiler is 32-bits)! SizeOf() function only returns the size of that pointer, not size of the data that pointer refers to. Also that is why we have Length() function for getting size of strings and dynamic arrays, and TObject.InstanceSize class method for getting size of an object.Hence, for data types as objects, interfaces, strings, or dynamic arrays; we can say practically they are always passed by reference, even if no modifier is specified for them. So the code bellow directly affects StringList variable, and there is no local copy of that object inside Foo:
program Test03 uses Classes; procedure Foo(Param1: TStrings); begin Param1 . Add(‘۱۲۳۴’); //=> This will directly add a new item to StringList. end ; var StringList : TStringList; begin StringList : TStringList . Create; try Foo(StringList); WriteLn (StringList . Text); //=> Writes ‘۱۲۳۴’. finally StringList . Free; end ; end . |
Also Const modifier has no particular meaning on objects, interfaces or dynamic arrays, because it only locks the pointer behind the scene, not the data structure to which the pointer is referring. So if we change declaration of Foo() in the code above to this:
procedure Foo( const Param1: TStrings); |
However, const and var modifiers have meaning for string types! This is because strings are a special auto-managed data types in Delphi.
Strings, Dynamic Arrays, and Interfaces are referenced counted:
Strings and dynamic arrays are both reference-counted. Interfaces in Delphi also provide reference-counting support). Reference-counted types are automatically handled by Delphi runtime. When you define a string variable in Delphi and assign it a value, you will have a data structure containing your text, and a reference-counter value which is set to 1:var A : string ; begin A := ‘Test’; // Memory is allocated for A // on the heap, and reference // counter is set to 1. end ; |
var A, B : string ; begin A := ‘Test’; // Memory is allocated for A // on the heap, and reference // counter is set to 1. B := A; // Reference count for the data // structure referred by A is incremented to 2. end ; |
Strings use Copy-On-Write technique:
The biggest reason why Const and Var modifiers are meaningful to string types is their copy-on-write feature. To explain this, I had to explain reference-counting in brief. Now please take a look at this example:var A, B : string ; begin A := ‘Test’; // Memory is allocated for A // on the heap, and reference // counter is set to 1. B := A; // Reference count for the data // structure referred by A is incremented to 2. B := B + ‘er’; // B = Tester , A = Test end ; |
This is called copy-on-write; as long as there is no modification, there is no need to do the costly copy operation and waste the memory. Delphi waits until a modification is requested, at that time, it does the actual copy operation.
Please take note, this mechanism does not exist for dynamic arrays, so if A and B in the code above were dynamic arrays, changing B would have affected A too.
So, how does a string parameter work?
When a string parameter is defined with no modifier, it is sent to the function as a reference to the actual data, but its reference-counter would be incremented when entering the function, and decremented when exiting the function. If a modification is done on the string parameter, then a new local string variable would be created inside the function, and the original value of the parameter would be copied to it (Copy-on-write operation). The change would be reflected on the local string variable. This local string variable would be released when execution of the function is over.When a string parameter is defined as a constant, compiler is sure that the string cannot be modified inside the function, therefore, there is no need for it to add codes for automatic incrementing and decrementing of the reference-counter.
When a string parameter is defined with var modifier, then it is sent to the function as a reference to the actual data. When value of the parameter is changed, then the original data would be copied to a new memory location, and the modification would be applied to it in the new memory location:
program Test04; procedure Test( var Param: string ); begin Param := Param + Param; end ; var A, B : string ; begin A := 'Test' ; B := A; Test(A); Writeln (A); //=> A is modifed and is now “TestTest” Writeln (B); //=> B still refers to “Test” end . |
When should we use Const modifier for parameters?
-
Whenever you have a parameter of type array or record, and you do not need to modify it inside your function; define it as a constant parameter to prevent expensive copy operation of array elements every time your function is called.
-
Whenever you have a parameter of type string or dynamic array, and you do not need to modify it inside your function, define it as a constant parameter to prevent compiler from generating protecting code to keep track of the reference-counter. When you do not use const modifier in such cases, compiler would add a hidden try-finally block to your function which contains code for keeping track of reference-counter for your string or dynamic array parameter. This would affect performance of your code. To read more about the impact of this, and see how much code compiler would produce in that case, you can read Eric Grange’s post on this.
OK, I hope this post is clarifying function parameter modifiers and their effects in Delphi programming; specially answering the question of when to use Const modifier.
Have Fun!
No comments:
Post a Comment