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

时间:2016-03-07 17:44:23

标签: 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.

此代码提供此调试输出:

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

一个小小的补充......

Here写道:

  

对于静态数组,记录和设置结果,如果值占用一个   字节在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)

1 个答案:

答案 0 :(得分:5)

最重要的一点是,两个函数都无法完全初始化返回值。一个function return value is not initialized,所以你不应假设它在进入时的价值。

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

function SomeFunc(Value: Integer): TSomeRec;

转化为

procedure SomeFunc(Value: Integer; var Result: TSomeRec);

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

Foo := SomeValue(42);

您希望将其转化为:

SomeValue(42, Foo);

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

var
  Temp: TSomeRec;
....
SomeValue(42, Temp);
Foo := Temp;

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

可能还有其他原因。

一个非常相关的问题,可能是重复的,可以在这里找到:Is it necessary to assign a default value to a variant returned from a Delphi function?这个问题和这个问题之间的一个关键区别是,那里考虑的类型是托管的,所以总是默认初始化,即使对于本地变量(隐藏或其他)。

但实际情况是,这完全是实施细节的问题。您需要了解function return values are not initialized,并且每个函数都必须初始化其返回值。