【问题标题】:Strange behavior with a record as a result of the function函数导致记录的奇怪行为
【发布时间】:2016-06-21 09:30:12
【问题描述】:

示例代码:

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)

【问题讨论】:

  • 我不认为这是重复的 - 正在初始化的返回值是答案的一部分,但我认为 OP 期望 implicit 类运算符的行为与它不同。具体来说,他们似乎期望result 将预先加载左侧变量的记录内容(即:运算符将为零碎的赋值/替换提供目标记录)。事实并非如此。
  • 我没有把它读进去,我看不出作为骗子关闭有什么不好。我们被积极鼓励这样做。建立这些链接对每个人都有帮助。除了未初始化的返回值之外,我看不出还有什么其他的。
  • @Vasek 您的重置方法正在自找麻烦。改变值的值类型的方法确实容易引起混淆。更喜欢使用简单的常量赋值。
  • @DavidHeffernan 对我来说,这似乎是对运算符重载的基本误解。在这里,当进行像GSomeRec := 1 这样的赋值时,OP 似乎期望函数中的result 将引用运算符左侧的变量(即:运算符重载提供了仅部分修改记录的机会分配给而不是要求操作员分配给或完全初始化新记录以进行分配)。幸运的是,这适用于局部变量(至少在这种情况下),但行为通常是未定义的。
  • 只有两个函数返回一个未初始化的值。

标签: delphi


【解决方案1】:

最重要的一点是您的两个函数都未能完全初始化返回值。 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,每个函数都必须初始化它的返回值。

【讨论】:

  • 关于初始化 - 忘了它)))关于 ABI - 这就是我需要的。在实际应用中,我有自己的色彩记录。我希望用隐式操作符来写这个记录,它复制了没有 alpha 通道的 TColor 类型。关于你的回答:我正确理解,对于非局部变量,编译器总是创建临时变量,但不能保证。
  • 底线是您不能使用返回值将信息传递给函数。所以我相信这个问题都是关于初始化的。返回值未初始化。所以你必须完全初始化它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-21
  • 1970-01-01
相关资源
最近更新 更多