1

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?

2
  • 1
    I suggest you make a reference to the list after assignment and try again. Commented Mar 16, 2022 at 7:03
  • 1
    There's no issue here at all. Because the array only ever has a reference count of one, the same block of memory can be re-used. Commented Mar 16, 2022 at 7:18

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

1 Comment

I see what I was missing now, thanks very much.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.