2016-03-07 67 views
1

示例代码的结果奇怪的行为:有记录的功能

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参数(作为expecte d)。
  • 对于GSomeRec和FSomeRec编译器,创建临时变量,在为正常变量赋值后传递她。

这是正常的吗?如果是正常的,请给我链接到规范(文档)。

PS

添加少量...

Here上写着:

对于静态数组,记录,并设置结果,如果值占用一个字节 它在AL中返回;如果该值占用两个字节,则在AX中返回 ;如果该值占用4个字节,则返回EAX中的 。否则,结果被传递给函数的声明参数

但事实上,这条规则是不成立后,一个额外的变量参数 返回。如果它包含调试器输出如下:

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

我不认为这是重复的 - 返回值被初始化是答案的一部分,但我认为OP期望隐式类操作符的行为与它的行为不同。具体来说,他们似乎预计'result'会预先加载左侧变量的记录内容(即:操作员将提供零碎分配/替换的目标记录)。这根本不是那么回事。 –

+0

我没有把它写进去,而且我也没有看到作为一个笨蛋关闭它有什么不好。我们积极鼓励这样做。每个人都可以建立这些链接。我看不出有什么比返回值没有被初始化更多的东西。 –

+0

@Vasek您的重置方法需要麻烦。变异值的值类型的方法往往会造成混淆。倾向于使用简单的常量赋值。 –

回答

5

最重要的一点是你的函数无法完全初始化返回值。 A function return value is not initialized,所以你不应该假设任何关于它的入门价值。

你是正确的观察到,德尔福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,并且每个函数都必须初始化其返回值。

+0

关于初始化 - 忘了它)))关于ABI - 这是我需要的。在实际应用中,我有自己的颜色记录。我希望用隐式运算符来编写这个记录,它们复制了没有alpha通道的TColor类型。关于你的回答:我正确的理解,对于非本地变量编译器总是会创建临时变量,但不能保证。 – Vasek

+1

底线是您不能使用返回值将信息传递给函数。所以我确实相信这个问题都是关于初始化。返回值没有被初始化。所以你必须完全初始化它们。 –