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.