测试中的Result
并非始终初始为()
我发现我需要在初始化时设置动态数组的长度吗?,但是我不完全理解这个答案
更重要的是,“断开”Result/A连接的最佳方法是什么(我需要循环)?也许有某种方法可以强制编译器“正确”初始化?手动添加Result := nil
作为Test中的第一行?
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;
2条答案
按热度按时间4c8rllxm1#
引用的问题是关于类内部的字段,它们都是零初始化的,并且托管类型在示例析构期间正确地终结。
你的代码是关于在循环内调用具有托管返回类型的函数。托管类型的局部变量被初始化一次-在例程的开始。底层的托管返回类型被编译器视为var参数。因此在第一次调用之后,它将看起来像
A
的内容传递给Test
两次-作为A
参数和Result
参数。但是你关于修改
Result
也会影响A
(参数)的评估是不正确的,我们可以通过稍微修改代码来证明这一点:当你单步执行
Test
时,你会看到改变Result[0]
不会改变A
。这是因为SetLength
会创建一个副本,因为编译器引入了第二个变量,它临时用来传递Result,在调用Test
之后,它会把Result赋值给A
。(局部变量)-你可以在反汇编视图中看到,循环中的这一行看起来与此类似(我使用$O+使代码比没有优化的情况下更密集):已知默认调用约定是eax、edx和ecx中的前三个参数,我们知道eax是
A
参数,edx是I
,ecx
是Result
(前面提到的Result var参数总是最后一个)。我们看到它使用了堆栈上的不同位置([ebp-$04]
是A
变量,[ebp-$08]
是编译器引入的变量)。在调用之后,我们看到编译器插入了一个对System._DynArrayAsg
的额外调用,然后将编译器为Result
引入的临时变量分配给A
。下面是第二次调用
Test
的屏幕截图:rkttyhzu2#
虽然我很犹豫是否将此
For
编译器优化称为bug,但如果直接修改数组元素,这肯定是没有帮助的:经过调查,我得出结论,影响整个数组的函数(例如,
SetLength
、Copy
或其他返回TArray<Integer>
的函数)将“破坏”For
循环创建的Result/A一致性。看起来最安全的方法是(根据原始帖子中链接的答案)将
Result := nil;
作为测试的第一行。如果没有其他建议,我最终会接受这个答案。
注意:作为一个额外的好处,从
Result := nil
开始可以防止数组被SetLength
复制--这是显而易见的,但是对于一个100000的数组来说,这个小小的修改可以使执行时间加快40