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,编译器创建临时变量,并在为普通变量赋值后传递她和
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类型。关于您的回答:我正确理解,对于非局部变量,编译器总是创建临时变量,但这并不能保证。底线是,您不能使用返回值将信息传递给函数。所以我相信这个问题都是关于初始化的。返回值未初始化。所以您必须完全初始化它们。