AJ.
AJ.

Reputation: 11250

Delphi dynamic array variable reuse

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?

Upvotes: 1

Views: 317

Answers (1)

Philip J. Rayment
Philip J. Rayment

Reputation: 1245

"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.

Upvotes: 1

Related Questions