Correct use of var in function calls?

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Correct use of var in function calls?

Bo Berglund
I am a bit confused over the way to specify parameters to function
calls as var or not....

I have the following constructs:

type
  TByteArr = array of byte;

I have a buffer class that receives data from a serial port and now I
want to extract data from this buffer to various types of variables in
the main application class.
So far I have managed all the simple variable types successfully, the
only remaining part is the handling of a body of varying length of
data. In my main class the data will wind up in a dynamic array of
bytes (TByteArr as above).

In the main class:

function xxxx.GetData(Dest:TByteArr): boolean; <== ????
....calculate length of data in bytes as Len....
  SetLength(Dest, Len);
  if not FRxBuf.Read(Dest) then exit;
  ....

In the buffer class I have a number of overloaded Read functions:

  function Read(Dest: Pointer; Count: Cardinal): Cardinal; overload;
  function Read(var Data: TByteArr): boolean; overload;
  ... and 5 more....

implemented as:

function TSSCommBuf.Read(var Data: TByteArr): boolean; // <== ???
begin
  Result := Read(@Data, SizeOf(Data)) = SizeOf(Data);
end;

function TSSCommBuf.Read(Dest: Pointer; Count: Cardinal): Cardinal;
var
  num: Cardinal;
  Src: Pointer;
begin
  num := FWriteIndex - FReadIndex;  //Remaining data bytes in buffer
  if num >= Count then
    num := Count;
  if num > 0 then
  begin
    Src := @FBuf[FReadIndex];  // FBuf is a TByteArr sized on demand
    Move(Src^, Dest^, num);
    FReadIndex := FReadIndex + num;
  end;
  Result := num;
end;

My question concerns the lines marked with <== ???
Do I use var in the declaration or not? As you can see the similar
functions are differently written now.
In Delphi I think that if an object or a dynamic array is passed as
argument it does not need the var declaration, but I am not sure.
I want to treat the array as a variable because it is the return
container for the data.


Bo Berglund

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

leledumbo
Administrator
FPC is Delphi compatible regarding this. Dynamic arrays are passed by reference, so you don't need var here. Please be aware when you directly play with dynamic array contents, since it's actually a pointer to a record in heap and it's reference counted.
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Bo Berglund
On Fri, 4 Feb 2011 06:41:24 -0800 (PST), leledumbo
<[hidden email]> wrote:

>
>FPC is Delphi compatible regarding this. Dynamic arrays are passed by
>reference, so you don't need var here. Please be aware when you directly
>play with dynamic array contents, since it's actually a pointer to a record
>in heap and it's reference counted.

OK, what will happen if I have a declaration like this:
function TSSCommBuf.Read(var Data: TByteArr): boolean;
as opposed to
function TSSCommBuf.Read(Data: TByteArr): boolean;

Will they be equivalent or will there be an "extra layer" of pointer??


Bo Berglund

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Jonas Maebe-2

On 04 Feb 2011, at 16:25, Bo Berglund wrote:

> OK, what will happen if I have a declaration like this:
> function TSSCommBuf.Read(var Data: TByteArr): boolean;
> as opposed to
> function TSSCommBuf.Read(Data: TByteArr): boolean;
>
> Will they be equivalent or will there be an "extra layer" of pointer??

They are different. And it's not just "an extra layer of pointer", both declarations allow you to do different things. In the second case, you make a copy of the dynamic array and hence its reference count is increased. In the first case, you pass in the original dynamic array in, and hence
a) no increase in reference count
b) if you do e.g. "data:=nil", then the variable that was passed in will set to nil and the reference count of the dynamic array will decrease (and if it becomes zeroed, it will be freed)


Jonas_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Bo Berglund
On Fri, 4 Feb 2011 19:10:33 +0100, Jonas Maebe
<[hidden email]> wrote:

>
>On 04 Feb 2011, at 16:25, Bo Berglund wrote:
>
>> OK, what will happen if I have a declaration like this:
>> function TSSCommBuf.Read(var Data: TByteArr): boolean;
>> as opposed to
>> function TSSCommBuf.Read(Data: TByteArr): boolean;
>>
>> Will they be equivalent or will there be an "extra layer" of pointer??
>
>They are different. And it's not just "an extra layer of pointer",
>both declarations allow you to do different things. In the second case,
>you make a copy of the dynamic array and hence its reference count is
>increased. In the first case, you pass in the original dynamic array in,
>and hence
>a) no increase in reference count
>b) if you do e.g. "data:=nil", then the variable that was passed in will
>set to nil and the reference count of the dynamic array will decrease
>(and if it becomes zeroed, it will be freed)
>

I made a test in Delphi7 as follows:

type
  TByteArr = array of byte;
...

function TForm1.FillArray(Arr: TByteArr): boolean;
var
  i, Len: integer;
begin
  Len := Length(Arr);
  for i := 0 to Len-1 do
    Arr[i] := i mod 256;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Len: Cardinal;
  i, s1, s2: integer;
  FLocal: TByteArr;
  P1, P2: Pointer;
begin
  Len := speSize.Value;
  SetLength(FBuf, Len);
  SetLength(FLocal, Len);
  FillArray(FLocal);
  s1 := Length(FBuf);

When I reach this position the debugger shows that FLocal contains the
data (0,1,2,3,4,5,6.....) which were entered by the FillArray
function, which seems to contradict your statement that a *copy* of
the array is made for use in the called function.

I then tried two other variations of the same function:

function TForm1.FillArray(var Arr: TByteArr): boolean;

function TForm1.FillArray(const Arr: TByteArr): boolean;

In all these cases the data entered into the array in the FillArray
function remain when the call returns. THe array contains (0,0,0,0...)
before the calls and (0,1,2,3,4,5...) after the calls.


To me it looks like the dynamic array is *always* passed by
*reference* to the FillArray function in Delphi 7....

So is there a difference here between Delphi7 and FPC?

It is difficult to check this in Lazarus since the debugger is not as
functional as in Delphi....


--
Bo Berglund
Developer in Sweden

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

michael.vancanneyt


On Sat, 5 Feb 2011, Bo Berglund wrote:

> On Fri, 4 Feb 2011 19:10:33 +0100, Jonas Maebe
> <[hidden email]> wrote:
>
>>
>> On 04 Feb 2011, at 16:25, Bo Berglund wrote:
>>
>>> OK, what will happen if I have a declaration like this:
>>> function TSSCommBuf.Read(var Data: TByteArr): boolean;
>>> as opposed to
>>> function TSSCommBuf.Read(Data: TByteArr): boolean;
>>>
>>> Will they be equivalent or will there be an "extra layer" of pointer??
>>
>> They are different. And it's not just "an extra layer of pointer",
>> both declarations allow you to do different things. In the second case,
>> you make a copy of the dynamic array and hence its reference count is
>> increased. In the first case, you pass in the original dynamic array in,
>> and hence
>> a) no increase in reference count
>> b) if you do e.g. "data:=nil", then the variable that was passed in will
>> set to nil and the reference count of the dynamic array will decrease
>> (and if it becomes zeroed, it will be freed)
>>
>
> I made a test in Delphi7 as follows:
>
> type
>  TByteArr = array of byte;
> ...
>
> function TForm1.FillArray(Arr: TByteArr): boolean;
> var
>  i, Len: integer;
> begin
>  Len := Length(Arr);
>  for i := 0 to Len-1 do
>    Arr[i] := i mod 256;
> end;
>
> procedure TForm1.Button1Click(Sender: TObject);
> var
>  Len: Cardinal;
>  i, s1, s2: integer;
>  FLocal: TByteArr;
>  P1, P2: Pointer;
> begin
>  Len := speSize.Value;
>  SetLength(FBuf, Len);
>  SetLength(FLocal, Len);
>  FillArray(FLocal);
>  s1 := Length(FBuf);
>
> When I reach this position the debugger shows that FLocal contains the
> data (0,1,2,3,4,5,6.....) which were entered by the FillArray
> function, which seems to contradict your statement that a *copy* of
> the array is made for use in the called function.
>
> I then tried two other variations of the same function:
>
> function TForm1.FillArray(var Arr: TByteArr): boolean;
>
> function TForm1.FillArray(const Arr: TByteArr): boolean;
>
> In all these cases the data entered into the array in the FillArray
> function remain when the call returns. THe array contains (0,0,0,0...)
> before the calls and (0,1,2,3,4,5...) after the calls.
>
>
> To me it looks like the dynamic array is *always* passed by
> *reference* to the FillArray function in Delphi 7....

No. Someone misunderstands the concept of dynamic array here.

A "dynamic array" is a pointer to an array in memory.
So when passing a dynamic array to a function,
you are, in fact, passing a pointer.

> So is there a difference here between Delphi7 and FPC?

No.

Test the following program:

Var
   A,B : Array of byte;
   I : integer;

begin
   SetLength(A,10);
   For I:=0 to 9 do
     A[I]:=I+1;
   B:=A; // A pointer is copied.
   B[5]:=33;
   Writeln(A[5]);
end.

This will write 33.

All this is explained in the Delphi (and FPC) documentation.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Bo Berglund
On Sat, 5 Feb 2011 09:24:44 +0100 (CET),
[hidden email] wrote:

>
>No. Someone misunderstands the concept of dynamic array here.
>
>A "dynamic array" is a pointer to an array in memory.
>So when passing a dynamic array to a function,
>you are, in fact, passing a pointer.
>
>> So is there a difference here between Delphi7 and FPC?
>
>No.
>

So in effect it means that all of these calls are exactly the same:

FillArr(const Arr: TByteArr)
FillArr(var Arr: TByteArr)
FillArr(Arr: TByteArr)

At least as long as the FillArr function does not attempt to change
the ponter itself like this:

Arr := SomeOtherArr;

In which case I guess that the only version that will carry this back
to the caller is the var version?

But that is not what I am doing at all, so I can stick with a simple:

FillArr(Arr: TByteArr)

and be sure that I will not get back a different array, but instead
get my array filled as requested...


--
Bo Berglund
Developer in Sweden

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Pointer to first dynamic array element (was: Re: [fpc-pascal] Correct use of var in function calls?)

Michael Müller-10
In reply to this post by Bo Berglund
Hi Bo!

Am 03.02.2011 um 22:53 schrieb Bo Berglund:

> function TSSCommBuf.Read(var Data: TByteArr): boolean; // <== ???
> begin
>  Result := Read(@Data, SizeOf(Data)) = SizeOf(Data);
> end;

When using dynamic arrays @Data will not be the address of the first array element which is usually what you want in combination with Read(). Since the dynamic array stores its length at the beginning with @Data you would overwrite the length value too.

So you have to use @Data[0]. Since this works also for static arrays you can / should do it always this way. This makes it also much easier to switch later from a static to a dynamic array.

A further hint for another example from you: When looping through the array use

for I := Low(Data) to High(Data)

instead of Len := Length(Data) and looping to Len - 1. Let the compiler do defining this local variable and the calculation.

Regards

Michael

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Michael Van Canneyt
In reply to this post by Bo Berglund


On Sat, 5 Feb 2011, Bo Berglund wrote:

> On Sat, 5 Feb 2011 09:24:44 +0100 (CET),
> [hidden email] wrote:
>
>>
>> No. Someone misunderstands the concept of dynamic array here.
>>
>> A "dynamic array" is a pointer to an array in memory.
>> So when passing a dynamic array to a function,
>> you are, in fact, passing a pointer.
>>
>>> So is there a difference here between Delphi7 and FPC?
>>
>> No.
>>
>
> So in effect it means that all of these calls are exactly the same:
>
> FillArr(const Arr: TByteArr)
> FillArr(var Arr: TByteArr)
> FillArr(Arr: TByteArr)
>
> At least as long as the FillArr function does not attempt to change
> the ponter itself like this:
>
> Arr := SomeOtherArr;

Indeed.

>
> In which case I guess that the only version that will carry this back
> to the caller is the var version?

Yes.

>
> But that is not what I am doing at all, so I can stick with a simple:
>
> FillArr(Arr: TByteArr)
>
> and be sure that I will not get back a different array, but instead
> get my array filled as requested...

It should, yes.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Florian Klämpfl
In reply to this post by Bo Berglund
Am 05.02.2011 10:46, schrieb Bo Berglund:
> But that is not what I am doing at all, so I can stick with a simple:
>
> FillArr(Arr: TByteArr)
>
> and be sure that I will not get back a different array, but instead
> get my array filled as requested...
>

As soon as you call SetLength, this will break havoc. Then a deep copy
is generated and it gets ref. count of 1 and it is destroyed at callee exit.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Pointer to first dynamic array element (was: Re: Correct use of var in function calls?)

Bo Berglund
In reply to this post by Michael Müller-10
On Sat, 5 Feb 2011 12:47:53 +0100, Michael Müller
<[hidden email]> wrote:

>Hi Bo!
>
>Am 03.02.2011 um 22:53 schrieb Bo Berglund:
>
>> function TSSCommBuf.Read(var Data: TByteArr): boolean; // <== ???
>> begin
>>  Result := Read(@Data, SizeOf(Data)) = SizeOf(Data);
>> end;
>
>When using dynamic arrays @Data will not be the address of the first
>array element which is usually what you want in combination with Read().
>Since the dynamic array stores its length at the beginning with @Data
>you would overwrite the length value too.
>
>So you have to use @Data[0]. Since this works also for static arrays
>you can / should do it always this way. This makes it also much easier
>to switch later from a static to a dynamic array.

I already did this in my *real* buffer class where I have 7 overloaded
Read methods for different datatypes. Needed this for string and
TByteArr.
Examples:

function TSSCommBuf.Read(var Data: Cardinal): boolean;
begin
  Result := Read(@Data, SizeOf(Data)) = SizeOf(Data);
end;

function TSSCommBuf.Read(Data: TByteArr): boolean;
begin
  Result := Read(@Data[0], Length(Data)) = Length(Data);
end;

function TSSCommBuf.Read(var Data: string; const Count: Cardinal):
boolean;
begin
  Result := Read(@Data[1], Count) = Count;
end;

All call the "real" Read method:

function TSSCommBuf.Read(Dest: Pointer; Count: Cardinal): Cardinal;
var
  num: Cardinal;
  Src: Pointer;
begin
  num := FWriteIndex - FReadIndex;  //Remaining data bytes in buffer
  if num >= Count then
    num := Count;
  if num > 0 then
  begin
    Src := @FBuf[FReadIndex];
    Move(Src^, Dest^, num);
    FReadIndex := FReadIndex + num;
  end;
  Result := num;
end;

>A further hint for another example from you: When looping through
>the array use
>
>for I := Low(Data) to High(Data)
>
>instead of Len := Length(Data) and looping to Len - 1. Let the
>compiler do defining this local variable and the calculation.
>

I know, but I use Move() for performance reasons in my real buffer
class. The other loops were only to check how the references were
working.


Bo Berglund

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Bo Berglund
In reply to this post by Florian Klämpfl
On Sat, 05 Feb 2011 15:51:37 +0100, Florian Klaempfl
<[hidden email]> wrote:

>Am 05.02.2011 10:46, schrieb Bo Berglund:
>> But that is not what I am doing at all, so I can stick with a simple:
>>
>> FillArr(Arr: TByteArr)
>>
>> and be sure that I will not get back a different array, but instead
>> get my array filled as requested...
>>
>
>As soon as you call SetLength, this will break havoc. Then a deep copy
>is generated and it gets ref. count of 1 and it is destroyed at callee exit.

I think I now encountered your warning live.....
What happened is that the caller has a dynamic array, which is set to
length 2 just as a precursor.
Then it calls a method and passes the name of the dynamic array as a
parameter. The function does some digging of data and then decides how
many elements to store, so it applies SetLength in its execution, then
fills the array with the new data.

But when the call returns the length of the array is still 2...

Turns out that this is a case when the declaration of the function
should have var tacked on like this:
function MyFunction(var Data: TByteArr): boolean;

When I added this the length was passed right back and it was possible
to read all of the data by the caller.

So in summary:
If the called method changes the length of teh dynamic array it must
be passed as a var, otherwise the length change will be lost when
exiting the method.


Bo Berglund

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

Florian Klämpfl
Am 06.02.2011 18:53, schrieb Bo Berglund:
>
> So in summary:
> If the called method changes the length of teh dynamic array it must
> be passed as a var, otherwise the length change will be lost when
> exiting the method.

I'd even propose that one uses var as soon as he plans to change the
array. It makes the code easier to understand.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Correct use of var in function calls?

michael.vancanneyt


On Sun, 6 Feb 2011, Florian Klämpfl wrote:

> Am 06.02.2011 18:53, schrieb Bo Berglund:
>>
>> So in summary:
>> If the called method changes the length of teh dynamic array it must
>> be passed as a var, otherwise the length change will be lost when
>> exiting the method.
>
> I'd even propose that one uses var as soon as he plans to change the
> array. It makes the code easier to understand.

Indeed. It indicates that you expect the array to be changed.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal