作为该函数的结果的记录的奇怪行为

Vas*_*sek 2 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;

  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.
Run Code Online (Sandbox Code Playgroud)

此代码提供此调试输出:

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)
Run Code Online (Sandbox Code Playgroud)

似乎不同变量的编译器创建了不同的代码:

  • LSomeRec它们作为var参数传递(如预期的那样).
  • 对于GSomeRec和FSomeRec编译器创建临时变量,传递她并为正常变量赋值后.

这是正常的吗?如果是正常的,请给我链接到规格(文件).

PS

一小部分......

在这里写道:

对于静态数组,记录和设置结果,如果该值占用一个字节,则在AL中返回; 如果该值占用两个字节,则在AX中返回; 如果值占用四个字节,则在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)
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 6

最重要的一点是,两个函数都无法完全初始化返回值.一个函数的返回值没有被初始化,所以你不应该承担有关其在进入任何有价值的东西.

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

function SomeFunc(Value: Integer): TSomeRec;
Run Code Online (Sandbox Code Playgroud)

变成了

procedure SomeFunc(Value: Integer; var Result: TSomeRec);
Run Code Online (Sandbox Code Playgroud)

但是,这并不意味着您可以对初始状态做出任何假设Result.当你写:

Foo := SomeValue(42);
Run Code Online (Sandbox Code Playgroud)

你期望这会变成:

SomeValue(42, Foo);
Run Code Online (Sandbox Code Playgroud)

如果Foo是局部变量,那么确实会发生这种情况.否则,虽然使用了隐藏的临时变量.代码转换为:

var
  Temp: TSomeRec;
....
SomeValue(42, Temp);
Foo := Temp;
Run Code Online (Sandbox Code Playgroud)

原因是编译器无法保证非局部变量有效.访问非本地可能会导致访问冲突.因此编译器的实现者决定使用临时本地,这样如果确实发生了访问冲突,那么它将在调用站点而不是在被调用者中引发.

而且可能还有其他原因.

可以在这里找到一个非常相关的问题,可能是重复的:是否需要为从Delphi函数返回的变量分配默认值?这个问题和这个问题之间的一个关键区别是,被考虑的类型是托管的,因此总是默认初始化,即使对于局部变量(隐藏或其他).

但实际情况是,这完全是实现细节的问题.您需要了解函数返回值未初始化,并且每个函数都必须初始化其返回值.