Delphi 函数导致记录的奇怪行为

Delphi 函数导致记录的奇怪行为,delphi,Delphi,示例代码: unit Main; interface uses Winapi.Windows, System.SysUtils, Vcl.Forms; type TSomeRec = record SomeData: Integer; SomePtr: Pointer; procedure Reset; class operator Implicit(const SomeData: Integer): TSomeRec; end; TM

示例代码:

unit Main;

interface

uses
  Winapi.Windows, System.SysUtils, Vcl.Forms;

type

  TSomeRec = record
    SomeData: Integer;
    SomePtr: Pointer;

    procedure Reset;
    class operator Implicit(const SomeData: Integer): TSomeRec;
  end;

  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FSomeRec: TSomeRec;
  end;

var
  MainForm: TMainForm;
  GSomeRec: TSomeRec;

implementation

{$R *.dfm}

function SomeFunc(Value: Integer): TSomeRec;
begin
  OutputDebugString(PWideChar(Result.SomeData.ToString + ' : ' + Integer(Result.SomePtr).ToString));
  Result.SomeData := Value;
end;

{ TSomeRec }

procedure TSomeRec.Reset;
begin
  SomeData := 5;
  SomePtr  := nil;
end;

class operator TSomeRec.Implicit(const SomeData: Integer): TSomeRec;
begin
  OutputDebugString(PWideChar(Result.SomeData.ToString + ' : ' + Integer(Result.SomePtr).ToString));
  Result.SomeData := SomeData;
end;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);
var
  LSomeRec: TSomeRec;
begin
  LSomeRec.Reset;
  GSomeRec.Reset;
  FSomeRec.Reset;

  LSomeRec := 1;
  GSomeRec := 1;
  FSomeRec := 1;

  LSomeRec.Reset;
  GSomeRec.Reset;
  FSomeRec.Reset;

  LSomeRec := SomeFunc(1);
  GSomeRec := SomeFunc(1);
  FSomeRec := SomeFunc(1);
end;

end.
此代码提供以下调试输出:

Debug Output: 5 : 0 Process DPITest.exe (1764)
Debug Output: 172555996 : 1638080 Process DPITest.exe (1764)
Debug Output: 1 : 1638080 Process DPITest.exe (1764)
Debug Output: 5 : 0 Process DPITest.exe (1764)
Debug Output: 1 : 1638080 Process DPITest.exe (1764)
Debug Output: 1 : 1638080 Process DPITest.exe (1764)
不同变量的编译器似乎会创建不同的代码:

  • LSomeRec将其作为var参数传递(如预期)
  • 对于GSomeRec和FSomeRec,编译器创建临时变量,并在为普通变量赋值后传递她和
这正常吗?如果是正常的,你能给我链接到规范(文档),请

PS

一个小的补充

上面写着:

对于静态数组、记录和设置结果,如果值占用一个 以AL形式返回的字节;如果该值占用两个字节,则为 以斧头归还;如果值占用四个字节,则返回 EAX。否则,结果将在附加的var参数中返回 在声明的参数之后传递给函数的

但事实上,这一规则并没有得到满足。如果它保存调试器,则输出如下:

 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)

最重要的一点是,两个函数都无法完全初始化返回值。A,所以您不应该在输入时对其值进行任何假设

您观察到Delphi ABI将大返回值作为隐藏的
var
参数实现是正确的。所以

function SomeFunc(Value: Integer): TSomeRec;
变成

procedure SomeFunc(Value: Integer; var Result: TSomeRec);
但是,这并不意味着您可以对
结果的初始状态做出任何假设。当你写作时:

Foo := SomeValue(42);
您希望将其转换为:

SomeValue(42, Foo);
var
  Temp: TSomeRec;
....
SomeValue(42, Temp);
Foo := Temp;
如果
Foo
是一个局部变量,那么这就是实际情况。否则,尽管使用了隐藏的临时变量。代码转换为:

SomeValue(42, Foo);
var
  Temp: TSomeRec;
....
SomeValue(42, Temp);
Foo := Temp;
原因是编译器不能保证非局部变量是有效的。访问非本地服务器可能导致访问冲突。因此,编译器的实现者决定使用一个临时本地文件,这样,如果确实发生访问冲突,那么它将在调用站点而不是被调用方中引发

很可能还有其他原因

这里可以找到一个非常相关的问题,可能是一个重复的问题:这个问题和这个问题之间的一个关键区别是,这里考虑的类型是托管的,因此总是默认初始化的,即使对于局部变量(隐藏或其他)


但真正的问题是,这完全是实现细节的问题。您需要了解这一点,并且每个函数都必须初始化其返回值。

我认为这不是重复的-初始化返回值是答案的一部分,但我认为OP希望
隐式
类运算符的行为与其不同。具体来说,他们似乎期望
result
将预加载左侧变量的记录内容(即:运算符将为逐段分配/替换提供目标记录)。事实并非如此。我没有在书中读到这一点,我也不认为作为一个傻瓜结束有什么不好。我们被积极鼓励这样做。建立这些链接对每个人都有帮助。我看不出还有什么比返回值没有初始化更重要的了。@Vasek你的重置方法是在自找麻烦。改变值的值类型的方法确实容易引起混淆。我更喜欢使用一个简单的常量赋值。@DavidHeffernan在我看来,这似乎是对运算符重载的一个基本误解。这里,当进行类似于
GSomeRec:=1
的赋值时,OP似乎期望函数中的
结果
将是对运算符左侧变量的引用(即:运算符重载提供了仅部分修改分配给的记录的机会,而不是要求运算符分配给或完全初始化要分配的新记录)。幸运的是,这适用于局部变量(至少在这种情况下),但行为通常是未定义的。只有两个函数返回未初始化的值。关于初始化-忘了它)))关于ABI-这是我需要的。在实际应用中,我有自己的彩色记录。我希望用隐式操作符来写这条记录,它在没有alpha通道的情况下复制TColor类型。关于您的回答:我正确理解,对于非局部变量,编译器总是创建临时变量,但这并不能保证。底线是,您不能使用返回值将信息传递给函数。所以我相信这个问题都是关于初始化的。返回值未初始化。所以您必须完全初始化它们。