Home > Mobile >  Delphi dynamic array variable reuse
Delphi dynamic array variable reuse

Time:03-17

I've recently discovered an interesting "gotcha" regarding dynamic arrays in Delphi and was wondering of the best way to avoid the issue.

Let's say we have the following example where a dynamic array variable is reused:

function FillArray(Count: Integer): TArray<Integer>;
var
  i: Integer;
begin
  SetLength(result, Count);
  for i := 0 to Count - 1 do
    result[i] := i;
end;

procedure TfrmMain.Button1Click(Sender: TObject);
var
  list: TArray<Integer>;
begin
  list := FillArray(5);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := FillArray(8);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := FillArray(12);
  meLog.Lines.Add(IntToStr(NativeInt(list)));
end;

As a function's result variable is just an implict var parameter the list variable is being reused and as the subsequent size of the array is being increased, 5 to 8 and then to 12, then the array is being reallocated, thus the output is:

2138845992
2138930232
2138887416

But if I start with creating with a size of 12 first:

  list := FillArray(12);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := FillArray(8);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := FillArray(5);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

then the same dynamic array is reused, as no reallocation is needed, the output is then:

2138887416
2138887416
2138887416

This is a nasty "gotcha" as if I store each assignment of list somewhere else then I am not getting unique arrays.

This is easy to avoid, I can either do:

function FillArray(Count: Integer): TArray<Integer>;
var
  i: Integer;
begin
  result := nil;
  SetLength(result, Count);
  for i := 0 to Count - 1 do
    result[i] := i;
end;

or

  list := nil;
  list := FillArray(12);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := nil;
  list := FillArray(8);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := nil;
  list := FillArray(5);
  meLog.Lines.Add(IntToStr(NativeInt(list)));

or

  list := Copy(FillArray(12));
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := Copy(FillArray(8));
  meLog.Lines.Add(IntToStr(NativeInt(list)));

  list := Copy(FillArray(5));
  meLog.Lines.Add(IntToStr(NativeInt(list)));

any of these will give me a unique array. The best seems to be result := nil, as you would assume such a function should return a unique array. But setting result := nil, then doing a setLength just looks wrong and someone not understanding the issue may remove the result := nil in the future thinking it is redundant.

So my question is, is my understanding of this correct and is there a better way to create a unique dynamic array?

CodePudding user response:

"if I store each assignment of list somewhere else then I am not getting unique arrays."

If you take a copy of each list, then the list is not reused, because dynamic arrays are reference-counted.

Run your test again with this code:

procedure TfrmMain.Button1Click(Sender: TObject);
var
  list,
  list1,
  list2: TArray<Integer>;
begin
  list := FillArray(12);
  meLog.Lines.Add(IntToStr(NativeInt(list)));
  list1 := list;

  list := FillArray(8);
  meLog.Lines.Add(IntToStr(NativeInt(list)));
  list2 := list;

  list := FillArray(5);
  meLog.Lines.Add(IntToStr(NativeInt(list)));
end;

The log will show different values each time.

  • Related