Delphi动态数组变量重用

发布于 2025-01-14 14:58:37 字数 2273 浏览 0 评论 0原文

我最近发现了一个关于 Delphi 中动态数组的有趣“陷阱”,并且想知道避免该问题的最佳方法。

假设我们有以下示例,其中重用了动态数组变量:

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;

作为函数的 result 变量只是一个隐式 var 参数,list 变量正在被重用,并且随着数组的后续大小增加,从 5 增加到 8,然后增加到 12,然后数组将被重新分配,因此输出为:

2138845992
2138930232
2138887416

但如果我首先以 12 的大小开始创建:

  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)));

那么相同的动态数组是重用,因为不需要重新分配,输出如下:

2138887416
2138887416
2138887416

这是一个令人讨厌的“陷阱”,就好像我将 list 的每个分配存储在其他地方,然后我没有得到唯一的数组。

这很容易避免,我可以这样做:

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;

  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)));

其中

  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)));

任何一个都会给我一个唯一的数组。最好的似乎是 result := nil,因为您会假设这样的函数应该返回一个唯一的数组。但是设置 result := nil,然后执行 setLength 看起来是错误的,不理解问题的人可能会删除 result := nil 中的 result := nil未来认为这是多余的。

所以我的问题是,我对此的理解是否正确?是否有更好的方法来创建独特的动态数组?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

横笛休吹塞上声 2025-01-21 14:58:37

“如果我将列表的每个分配存储在其他地方,那么我就不会获得唯一的数组。”

如果您获取每个列表的副本,则该列表不会被重用,因为动态数组是引用计数的。

使用以下代码再次运行测试:

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;

日志每次都会显示不同的值。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文