我遇到了一些奇怪的 Delphi XE 3编译器的行为(我为x86架构编译)。
假设我有一个带有一个字段的类--带有几个简单类型字段的自定义记录:
TPage = class
type
TParagraph = record
public
FOwner: TPage;
FFirst: Integer;
FSecond: Integer;
procedure Select;
end;
public
FSelected: TParagraph;
end;
procedure TPage.TParagraph.Select;
begin
FOwner.FSelected:=Self;
end;
逻辑是我的页面可以包含几个段落,在某些时刻,我希望这些段落之一成为选定(能够做一些动作与它在我的程序的其他部分):
procedure TMainForm.Button1Click(Sender: TObject);
var
lcPage: TPage;
lcParagraph: TPage.TParagraph;
begin
lcPage:=TPage.Create;
try
<...>
lcParagraph.FOwner:=lcPage;
lcParagraph.FFirst:=1;
lcParagraph.FSecond:=2;
lcParagraph.Select;
<...>
finally
lcPage.Free;
end;
当我的记录不超过某个大小时,一切都是可以的。一个引用和两个整数是可以的,在这种情况下,我得到了如下的汇编指令:
MainUnit.pas.350: FOwner.FSelected:=Self;
00C117B3 8B45FC mov eax,[ebp-$04]
00C117B6 8B00 mov eax,[eax]
00C117B8 8B55FC mov edx,[ebp-$04]
00C117BB 8B0A mov ecx,[edx]
00C117BD 894804 mov [eax+$04],ecx
00C117C0 8B4A04 mov ecx,[edx+$04]
00C117C3 894808 mov [eax+$08],ecx
00C117C6 8B4A08 mov ecx,[edx+$08]
00C117C9 89480C mov [eax+$0c],ecx
我可以看到三个正确的mov,它们将内存从本地记录复制到类中。
**但是!**如果向记录中添加更多字段,则生成的asm代码将更改,并且记录分配将不再正确执行。
第一个
然后我在类的记录FSelected中得到垃圾:
执行莱亚指令后,CPU状态如下:
在这个例子中,02 D37280是我的lcPage类的地址,所以02 D37284应该包含它的字段- FSelected记录的开始。但是movsd指令将内存从ESI复制到EDI,从02 D37280复制到02 D37284,这完全没有意义!如果我将ESI寄存器的值更改为EAX(19 F308),这是我的本地lcParagraph变量的开始,复制操作正确执行:
我描述的是已知的bug吗?还是我遗漏了一些关于 Delphi 的基本信息?这是分配记录的好方法吗?我可以很容易地解决这个问题,例如,通过在procedure TPage.TParagraph.Select;
中将FOwner.FSelected:=Self;
改为CopyMemory(@FOwner.FSelected, @Self, SizeOf(Self));
。但是我想找出问题出在哪里。
最小可重现示例:
program RecordAssignmentIssue;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TPage = class
type
TParagraph = record
public
FOwner: TPage;
FFirst: Integer;
FSecond: Integer;
FThird: Integer;
procedure Select;
end;
public
FSelected: TParagraph;
end;
procedure TPage.TParagraph.Select;
begin
FOwner.FSelected:=Self;
end;
var
lcPage: TPage;
lcParagraph: TPage.TParagraph;
begin
try
lcPage:=TPage.Create;
try
lcParagraph.FOwner:=lcPage;
lcParagraph.FFirst:=1;
lcParagraph.FSecond:=2;
lcParagraph.FThird:=3;
lcParagraph.Select;
Assert(CompareMem(@lcPage.FSelected, @lcParagraph, SizeOf(lcParagraph)));
// get rid of FThird and assertion will pass
finally
lcPage.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
1条答案
按热度按时间qnyhuwrf1#
这是一个仍然存在于 Delphi 11中的bug(感谢LU RD的确认)。你应该提交一个bug报告到Quality Portal。
同时,我认为可以通过在
TPage
中而不是在TParagraph
中进行赋值来解决这个问题。如下所示:或者,另一个非常简单的解决方法就是引入一个额外的局部指针变量来保存指向
Self
的指针: