views:

963

answers:

2

I already read up about passing by reference and so

procedure test(var x:integer);
begin
  x:=x+5;
end;

so the above code updates 5 by reference. I assumed if I was updating an array by reference I could declare var X: array of blah... having some bound bugs and just wanted to know if I should be using the type of data for the pointer to the data or if the pointer is always int... just so I know if it's how I'm doing my passing by reference or something else in my code that is the issue.

+6  A: 

If you pass dynamic array as a non-var parameter, compiler will make a copy.

The small code sample below demonstrates that by displaying 37/42 in the form caption.

procedure IncArray1(data: array of integer);
var i : integer;
begin
  for i := Low(data) to High(data) do
    data[i] := data[i] + 5;
end;

procedure IncArray2(var data: array of integer);
var i : integer;
begin
  for i := Low(data) to High(data) do
    data[i] := data[i] + 5;
end;

procedure TForm8.FormCreate(Sender: TObject);
var
  data: array of integer;
begin
  SetLength(data, 1);
  data[0] := 37;
  IncArray1(data);
  Caption := IntToStr(data[0]);
  IncArray2(data);
  Caption := Caption + '/' + IntToStr(data[0]);
end;

If we look into the generated assembler code, IncArray1 starts with

004552B4 8BCA             mov ecx,edx
004552B6 85C9             test ecx,ecx
004552B8 7807             js $004552c1
004552BA 8B1C88           mov ebx,[eax+ecx*4]
004552BD 49               dec ecx
004552BE 53               push ebx
004552BF 79F9             jns $004552ba
004552C1 8BC4             mov eax,esp

This code copies source array to the stack and sets eax to the address of the first element (= address stored in the stack pointer after last push). Stack grows down so the code starts with the last element (edx contains High(data) when IncArray1 is called) and repeats (read element; push element; decrement index) until it gets to the element 0.

IncArray2 contains no such code. Caller stores the address of the data into the eax register before calling IncArray2 and IncArray2 just uses this address.

In case you don't want to use 'var' for any reason, you can pass an address of the data to your method. But as you can't use syntax 'data: ^array of integer' in parameter declaration, you'd have to declare a type for your data. And you'd have to use 'data^' instead of 'data' everywhere in the method.

type
  TData = array of integer;
  PData = ^TData;

procedure IncArray(data: PData);
var i : integer;
begin
  for i := Low(data^) to High(data^) do
    data^[i] := data^[i] + 5;
end;

procedure TForm8.FormCreate(Sender: TObject);
var
  data: TData;
begin
  SetLength(data, 2);
  data[0] := 37;
  IncArray(@data);
  Caption := IntToStr(data[0]);
end;

Tested with Delphi 2007.

gabr
Ok so is there a way to do it, i have a large array that i want to work on via reference. copying the array is far to comptationaly expensive.
Arthur
Yes, just like the IncArray2 does in the code above - user 'var' prefix.
gabr
@gabr, could you check if a dynamic array is copied when you don't mutate? I know that strings are copy-on-write.
Henk Holterman
Data is still copied. If you want to prevent Delphi to copy data, prefix your parameters with 'const'. Applies to record and strings and interfaces, too.
gabr
+2  A: 

Gabr's answer is correct but the key point is buried deep enough that I'll bring it out as a separate post:

Define your types first! In this specific case the compiler accepted an array of integer there, but that's only because that has a special meaning and it's NOT what you expected. Any other attempt to define a type in the procedure's definition would simply have failed.

Unlike C, if you want two things to be assignment compatible you have to declare them as being the SAME type, not merely two types that are built the same:

Var
     A : Array [1..4] of Integer;
     B : Array [1..4] of Integer;

Begin
    A := B;

Won't compile. Rather:

Type
    Array4 = array [1..4] of Integer;

Var
    A : Array4;
    B : Array4;

Begin
    A := B;

and the compiler does what you would expect.

Loren Pechtel
Having run away to change code after Gabr's post i now have the exact issue you are saying,, it wont come pile,, it says the formal types ahve to be the same.. A and B for me are in different units.. what can i do?
Arthur
Let's assume that unit A "uses" unit B. Declare the shared type in unit B, and both can "see" that definition.
Argalatyr
If you run gabr's code but with all the "array of integer" replaced by TIntArray (where TIntArray = array of integer), then the array is always passed by reference, and data[0] is 37 + 5 + 5 = 49. Isn't that kind of weird?
dangph