TArray Result not always initially () within for loop?

问题描述 投票:0回答:2
测试中的

Result
最初并不总是
()

我发现我需要在初始化时设置动态数组的长度吗?,但是我不完全理解那个答案

更重要的是,“中断”Result/A 连接(我需要循环)的最佳方法是什么?也许有某种方法可以强制编译器“正确”初始化?手动添加

Result := nil
作为测试中的第一行?

function Test(var A: TArray<Integer>): TArray<Integer>;
begin
  SetLength(Result, 3); // Breakpoint here
  Result[0] := 2;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  A: TArray<Integer>; // Does not help whether A is local or global
  I: Integer;
begin
  for I := 1 to 3 do
    A := Test(A); // At Test breakpoint:
                  // * FIRST loop: Result is ()
                  // * NEXT loops: Result is (2, 0, 0)
                  //               modifying Result changes A (immediately)
  A := Test(A);   // Result is again ()
end;
arrays loops for-loop delphi reference-counting
2个回答
0
投票

引用的问题是关于类内部的字段,它们都是零初始化的,并且托管类型在实例销毁期间正确完成。

您的代码是关于在循环中调用具有托管返回类型的函数。托管类型的局部变量在例程开始时初始化一次。底层的托管返回类型被编译器视为 var 参数。因此,在第一次调用之后,它将看起来像

A
的内容传递给
Test
两次 - 作为
A
参数和
Result
.

但是您认为修改

Result
也会影响
A
(参数)的评估是不正确的,我们可以通过稍微更改代码来证明:

function Test(var A: TArray<Integer>; I: Integer): TArray<Integer>;
begin
  SetLength(Result, 3); // Breakpoint here
  Result[0] := I;
end;

procedure Main;
var
  A: TArray<Integer>;
  I: Integer;
begin
  for I := 1 to 3 do
    A := Test(A, I);
                    
  A := Test(A, 0);
end;

当您单步执行

Test
时,您会看到更改
Result[0]
不会更改
A
。这是因为
SetLength
将创建一个副本,因为编译器引入了它临时用于传递结果的第二个变量,并且在调用
Test
之后它将其分配给
A
(局部变量) - 你可以在反汇编视图对于循环中的行看起来类似于此(我使用 $O+ 使代码比没有优化时更密集):

Project1.dpr.21: A := Test(A, I);
0041A3BD 8D4DF8           lea ecx,[ebp-$08]
0041A3C0 8D45FC           lea eax,
0041A3C3 8BD3             mov edx,ebx
0041A3C5 E8B2FFFFFF       call Test
0041A3CA 8B55F8           mov edx,[ebp-$08]
0041A3CD 8D45FC           lea eax,[ebp-$04]
0041A3D0 8B0DC8244000     mov ecx,[$004024c8]
0041A3D6 E855E7FEFF       call @DynArrayAsg
0041A3DB 43               inc ebx

知道默认调用约定是eax,edx,ecx中的前三个参数,我们知道eax是

A
参数,edx是
I
ecx
Result
(前面提到的Result var参数总是最后一个).我们看到它使用了堆栈上的不同位置(
[ebp-$04]
A
变量和
[ebp-$08]
是编译器引入的变量)。在调用之后,我们看到编译器插入了对
System._DynArrayAsg
的额外调用,然后将编译器为
Result
引入的临时变量分配给
A
.

这里是第二次调用

Test
的截图:


0
投票

虽然我犹豫是否将此

For
编译器优化称为错误,但如果直接修改数组元素,这肯定没有帮助:

function Test(var A: TArray<Integer>): TArray<Integer>;
begin
  if Length(Result) > 0 then // Breakpoint
    Result[1] := 66; // A modified!
  SetLength(Result, 3);
  Result[0] := Result[0] + 1; // A not modified
  Exit;
  A[9] := 666; // Force linker not to eliminate A
end;

经过调查,我得出结论,影响整个数组的函数(例如

SetLength
Copy
或其他返回
TArray<Integer>
的函数)将——不出所料——“打破”由
 创建的结果/A 相同性For
循环。

看来最安全的方法是(根据原始帖子中链接的答案)将

Result := nil;
作为测试的第一行。

如果没有进一步的建议,我最终会接受这个作为答案。

注意: 作为一个额外的好处,以

Result := nil
开头可以防止数组被
SetLength
复制——很明显,但是对于例如一个 100000 的数组被循环 100000 次这个小小的修改使执行时间快了约 40%

© www.soinside.com 2019 - 2024. All rights reserved.