gpt4 book ai didi

arrays - 这是预期的 Delphi 动态数组行为吗

转载 作者:行者123 更新时间:2023-12-03 14:47:45 24 4
gpt4 key购买 nike

问题是 - 当动态数组被设置为类成员时,它们是如何由 Delphi 内部管理的?它们是通过引用复制还是传递?使用 Delphi 10.3.3。
UpdateArray方法从数组中删除第一个元素。但数组长度保持为 2。UpdateArrayWithParam方法还会从数组中删除第一个元素。但是数组长度正确地减少到 1。

这是一个代码示例:

interface

type
TSomeRec = record
Name: string;
end;
TSomeRecArray = array of TSomeRec;

TSomeRecUpdate = class
Arr: TSomeRecArray;
procedure UpdateArray;
procedure UpdateArrayWithParam(var ParamArray: TSomeRecArray);
end;

implementation

procedure TSomeRecUpdate.UpdateArray;
begin
Delete(Arr, 0, 1);
end;

procedure TSomeRecUpdate.UpdateArrayWithParam(var ParamArray: TSomeRecArray);
begin
Delete(ParamArray, 0, 1);
end;

procedure Test;
var r: TSomeRec;
lArr: TSomeRecArray;
recUpdate: TSomeRecUpdate;
begin
lArr := [];

r.Name := 'abc';
lArr := lArr + [r];
r.Name := 'def';
lArr := lArr + [r];

recUpdate := TSomeRecUpdate.Create;
recUpdate.Arr := lArr;
recUpdate.UpdateArray;
//(('def'), ('def')) <=== this is the result of copy watch value, WHY two values?

lArr := [];

r.Name := 'abc';
lArr := lArr + [r];
r.Name := 'def';
lArr := lArr + [r];

recUpdate.UpdateArrayWithParam(lArr);

//(('def')) <=== this is the result of copy watch value - WORKS

recUpdate.Free;
end;

最佳答案

这是个有趣的问题!

Delete更改 dynamic array 的长度-- 正如 SetLength确实 - 它必须重新分配动态数组。并且它还将给它的指针更改为内存中的这个新位置。但显然它不能改变任何其他指向旧动态数组的指针。

所以它应该减少旧动态数组的引用计数并创建一个引用计数为1的新动态数组。给Delete的指针将被设置为这个新的动态数组。

因此,旧的动态数组应该保持不变(当然,它减少的引用计数除外)。这基本上是为类似的 SetLength function 记录的。 :

Following a call to SetLength, S is guaranteed to reference a unique string or array -- that is, a string or array with a reference count of one.



但令人惊讶的是,这在这种情况下并没有发生。

考虑这个最小的例子:
procedure TForm1.FormCreate(Sender: TObject);
var
a, b: array of Integer;
begin

a := [$AAAAAAAA, $BBBBBBBB]; {1}
b := a; {2}

Delete(a, 0, 1); {3}

end;

我选择了这些值,以便它们在内存中很容易被发现 (Alt+Ctrl+E)。

在(1)之后, a指向 $02A2C198在我的测试运行中:
02A2C190  02 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB

这里引用计数为 2,数组长度为 2,正如预期的那样。 (有关动态数组,请参阅 internal data format 的文档。)

在(2)之后, a = b ,即 Pointer(a) = Pointer(b) .它们都指向同一个动态数组,现在看起来像这样:
02A2C190  03 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB

正如预期的那样,引用计数现在是 3。

现在,让我们看看 (3) 之后会发生什么。 a现在指向一个新的动态数组 2A30F88在我的测试运行中:
02A30F80  01 00 00 00 01 00 00 00
02A30F88 BB BB BB BB 01 00 00 00

正如预期的那样,这个新的动态数组的引用计数为 1,并且只有“B 元素”。

我希望旧的动态数组,其中 b仍然指向,看起来像以前一样,但引用计数减少了 2。但现在看起来像这样:
02A2C190  02 00 00 00 02 00 00 00
02A2C198 BB BB BB BB BB BB BB BB

虽然引用计数确实减少到2,但是第一个元素已经改变了。

我的结论是

(1) 是 Delete契约(Contract)的一部分它使对初始动态数组的所有其他引用无效的过程。

或者

(2) 它应该像我上面概述的那样表现,在这种情况下这是一个错误。

不幸的是, Delete procedure 的文档根本没有提到这个。

感觉是个bug。

更新:RTL 代码

我看了 Delete的源代码程序,这是相当有趣的。

将行为与 SetLength 的行为进行比较可能会有所帮助(因为一个工作正常):
  • 如果动态数组的引用计数为 1,则 SetLength尝试简单地调整堆对象的大小(并更新动态数组的长度字段)。
  • 否则,SetLength为引用计数为 1 的新动态数组进行新的堆分配。旧数组的引用计数减 1。

  • 这样就保证了最终的引用计数总是 1 -- 要么是从一开始,要么是已经创建了一个新数组。 (您并不总是进行新的堆分配,这是一件好事。例如,如果您有一个引用计数为 1 的大数组,那么简单地将其截断比将其复制到新位置要便宜。)

    现在,由于 Delete总是使数组更小,尝试简单地减小堆对象的大小是很诱人的。这确实是 RTL 代码在 System._DynArrayDelete 中的尝试。 .因此,就您而言, BBBBBBBB移动到数组的开头。一切都很好。

    但随后它调用 System.DynArraySetLength , 也被 SetLength 使用.此过程包含以下注释,
    // If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy

    在它检测到对象确实是共享的(在我们的例子中,ref count = 3)之前,为一个新的动态数组分配一个新的堆,并将旧的(减少的)复制到这个新位置。它减少旧数组的引用计数,并更新新数组的引用计数、长度和参数指针。

    所以无论如何我们最终得到了一个新的动态数组。但是 RTL 程序员忘记了他们已经弄乱了原始数组,现在它由放置在旧数组之上的新数组组成: BBBBBBBB BBBBBBBB .

    关于arrays - 这是预期的 Delphi 动态数组行为吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60659730/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com