使用“隐式”类操作符对包含动态数组的Delphi记录进行初始化

21

我正试图弄清楚是否可以使用Delphi(Berlin 10.1 upd 1)中的“隐式”类运算符初始化包含动态数组的记录。

附加的程序产生以下输出:

ci iA r1 r2 r3
1  1  1  1  49694764491115752
2  2  2  2  11570520
3  3  3  3  0
4  4  4  4  0
5  5  5  5  0
  • TRec 是包含动态数组的记录类型,我想要初始化它。
  • ci 是一个整数常量数组。
  • ia 是一个整数动态数组。
  • r1,r2,r3 是类型为TRec的记录,它们以不同的方式被初始化。

从输出结果可以看出,使用常量进行前两个分配(r1、r2)的工作与预期一样。编译器接受第三个分配r3 := iArray,但结果是错误的。调试器显示在TRec.Implicit中的v值已经出现了问题。

这里出了什么问题?这可能吗?

program Project5;

{$APPTYPE CONSOLE}

{$R *.res}

type
  TRec = record
    iArray: array of UInt64;
    class operator Implicit(const v: array of UInt64): TRec;
  end;

{ TRec }

class operator TRec.Implicit(const v: array of UInt64): TRec;
var
  i: integer;
begin
  setlength(Result.iArray, Length(v));
  for i := 0 to High(v) do
    Result.iArray[i] := v[i];
end;

const
  ciArray: array [0 .. 4] of UInt64 = (1, 2, 3, 4, 5);

var
  i         : integer;
  iArray    : array of UInt64;
  r1, r2, r3: TRec;

begin
  iArray := [1, 2, 3, 4, 5];

  r1 := [1, 2, 3, 4, 5];
  r2 := ciArray;
  r3 := iArray;

  Writeln('ci iA r1 r1 r3');
  for I := 0 to High(ciArray) do
    Writeln(ciArray[i], '  ', iArray[i], '  ', r1.iArray[i], '  ', r2.iArray[i], '  ', r3.iArray[i]);

  readln;

end.

1
当然可以。我也为我的 BigInteger 类型(http://www.rvelthuis.de/programs/bigintegers.html) 这样做。但请确保执行某种写时复制(COW),因此每次修改 数组 时,您必须确保它是唯一的副本。 - Rudy Velthuis
@RudyVelthuis 编译器的错误导致这是不可能的,除非你绕过这个错误或从供应商那里获得修复。 - David Heffernan
好的,看起来Implicit()函数中的dynarray parameters是一个特殊情况。否则它可以正常工作。该记录可以包含一个dynarray。 - Rudy Velthuis
好的,动态数组参数是我们要讨论的主题,所以让我们专注于这个。 - David Heffernan
问题的主题是“使用dynarray和隐式运算符的记录”,而不是“带有开放数组参数的隐式运算符的记录”。这不一样。而且这似乎在Delphi 10.2 Tokyo中已经修复了。 - Rudy Velthuis
类似的错误:https://quality.embarcadero.com/browse/RSP-36156 - Gabriel
1个回答

19

看起来你在codegen中发现了一个错误(在Win64编译器中也存在)。我浏览了生成的汇编代码,似乎编译器对于运算符重载产生了错误指令。这就是为什么错误的值最终会出现在运算符重载内部的数组中。请在Quality Portal中报告此问题。

坏结果生成的代码:

Project109.dpr.46: r3 := iArray;
0040B1F2 A1FC044100       mov eax,[$004104fc]
0040B1F7 8945E8           mov [ebp-$18],eax
0040B1FA 837DE800         cmp dword ptr [ebp-$18],$00
0040B1FE 740B             jz $0040b20b
0040B200 8B45E8           mov eax,[ebp-$18]
0040B203 83E804           sub eax,$04
0040B206 8B00             mov eax,[eax]
0040B208 8945E8           mov [ebp-$18],eax
0040B20B 8D4DD8           lea ecx,[ebp-$28]
0040B20E 8B55E8           mov edx,[ebp-$18]
0040B211 4A               dec edx
0040B212 B8FC044100       mov eax,$004104fc // <-- wrong one
0040B217 E87CF5FFFF       call TRec.&op_Implicit

一个判断相等的方法代码:

Project109.dpr.47: r3 := TRec.Implicit(iArray);
0040B22F A1FC044100       mov eax,[$004104fc]
0040B234 8945E4           mov [ebp-$1c],eax
0040B237 837DE400         cmp dword ptr [ebp-$1c],$00
0040B23B 740B             jz $0040b248
0040B23D 8B45E4           mov eax,[ebp-$1c]
0040B240 83E804           sub eax,$04
0040B243 8B00             mov eax,[eax]
0040B245 8945E4           mov [ebp-$1c],eax
0040B248 8D4DD4           lea ecx,[ebp-$2c]
0040B24B 8B55E4           mov edx,[ebp-$1c]
0040B24E 4A               dec edx
0040B24F A1FC044100       mov eax,[$004104fc] // <-- correct one
0040B254 E8CFF5FFFF       call TRec.Implicit

然而,您可以通过为参数类型添加另一个隐式操作符重载TArray<UInt64>,然后将本地变量声明为该类型,以便编译器选择正确的重载(在这种情况下不会生成错误代码的重载)来避免此问题。

但是请注意,由于 Delphi 的严格类型规则,只有在传递TArray<UInt64> 类型的变量并调用正确的重载时才能起作用,如果您有任何其他动态 array of UInt64 ,则会调用错误的重载。

更新: 此缺陷已在Delphi 10.2 Tokyo中得到报告并得到修复。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接