Wednesday, July 3, 2019

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.

Passing by reference means, compiler only sends a pointer to the function. This pointer refers to the original data, so changing value of the parameter inside the function directly changes the original data. In Delphi such parameters are marked with var keyword:

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.

These are the two basic ways parameters are passed to functions, but Delphi has still two other modifier keywords for function parameters: const and out. What are these two used for?
“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.

Using var modifier on objects, interfaces or dynamic arrays is most of the time unnecessary, because the value itself is a reference, so using var means having a reference to another reference!
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);
We will get the same result as the above code.

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;

Now if you change the above code to this:

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;

Value of A is not copied to B, instead of that, B refers to the same data structure as A, and reference counter of that data structure is set to 2. As soon as any of those variables goes out of scope, reference counter of that data structure would be decremented, and when reference counter reaches down to zero, the data structure would be automatically freed. This mechanism is the same for strings and dynamic arrays. For interfaces, the class which implements the interface should implement three methods defined inside IInterface to provide reference-counting support.

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 the same as the previous sample code, with one extra line. In the last line, we add ‘er’ to variable B. That will result in B referring to “Tester”, not “Test” anymore. How is it done? A and B were both referring to a data structure containing “Test”. Then we decided to modify B. Delphi runtime assigns a new data structure, and copies the string “Test” and the string “er” to it. It also decrements reference-counter of original “Test”, and changes B to refer to the new data structure. It also increments reference-counter of the new data structure. So at the end, we have A which refers to the old data structure, and B which refers to the new data structure. Reference-counters of both data structures are 1 now.

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.
Now back to the original question:


When should we use Const modifier for parameters?

  1. 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.
  2. 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.
Important Note: For the second recommendation above to work, you have to set “String format checking” in your project option to False. This option adds that protective try-finally block to your functions whether you define your string or dynamic array as const or not! This is for backward compatibility with some old C++ Builder codes. If you are not a C++ Builder developer, or are a C++ Builder developer, but do not have such old codes, then you MUST set this option to False in your projects. It reduces performance of strings in Delphi.



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: