lol*_*rol 7 delphi delphi-xe3 delphi-10.4-sydney
我遇到了 delphi XE3 编译器的一些奇怪行为(我为 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;
Run Code Online (Sandbox Code Playgroud)
逻辑是我的页面可以包含多个段落,在某些时候我希望选择其中一个段落(以便能够在程序的其他部分中使用它执行一些操作):
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;
Run Code Online (Sandbox Code Playgroud)
当我的记录不超过一定大小时,一切都可以。一个引用和两个整数就可以了,在这种情况下,我得到如下汇编指令:
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
Run Code Online (Sandbox Code Playgroud)
我可以看到三个正确的 movs 将内存从本地记录复制到类。
但!如果我向记录中添加更多字段,生成的 asm 代码会发生更改,并且记录分配将不再正确执行。
TParagraph = record
public
FOwner: TPage;
FFirst: Integer;
FSecond: Integer;
FThird: Integer;
procedure Select;
end;
Run Code Online (Sandbox Code Playgroud)
MainUnit.pas.350: FOwner.FSelected:=Self;
00C117C9 8B45FC mov eax,[ebp-$04]
00C117CC 8B55FC mov edx,[ebp-$04]
00C117CF 8B12 mov edx,[edx]
00C117D1 8BF2 mov esi,edx
00C117D3 8D7A04 lea edi,[edx+$04]
00C117D6 A5 movsd
00C117D7 A5 movsd
00C117D8 A5 movsd
00C117D9 A5 movsd
Run Code Online (Sandbox Code Playgroud)
lea指令后CPU状态如下:
在此示例中,02D37280 是我的 lcPage 类的地址,因此 02D37284 应包含其字段的开头 - FSelected 记录。但是movsd指令将内存从ESI复制到EDI,从02D37280复制到02D37284,这绝对没有意义!如果我将 ESI 寄存器更改为 EAX (19F308) 的值(这是我的本地 lcParagraph 变量的开始),则复制将正确执行:

我所描述的是已知错误吗?或者我错过了delphi的一些基本知识?这是分配记录的好方法吗?我可以轻松解决该问题,例如,通过更改FOwner.FSelected:=Self;为CopyMemory(@FOwner.FSelected, @Self, SizeOf(Self));in procedure TPage.TParagraph.Select;。但我想弄清楚出了什么问题。
最小可重现示例:
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.
Run Code Online (Sandbox Code Playgroud)
这是 Delphi 11 中仍然存在的错误(感谢LU RD 确认)。您应该向Quality Portal提交错误报告。
与此同时,我认为您可以通过在TPage而不是在 中进行作业来解决这个问题TParagraph。像这样:
program RecordAssignmentIssue;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TPage = class
type
TParagraph = record
public
FOwner: TPage;
FFirst: Integer;
FSecond: Integer;
FThird: Integer;
procedure Select;
end;
private
procedure Select(const Paragraph: TParagraph);
public
FSelected: TParagraph;
end;
procedure TPage.TParagraph.Select;
begin
FOwner.Select(Self);
end;
{ TPage }
procedure TPage.Select(const Paragraph: TParagraph);
begin
FSelected:=Paragraph;
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.
Run Code Online (Sandbox Code Playgroud)
或者另一个非常简单的解决方法是引入一个额外的本地指针变量来保存指向的指针Self:
procedure TPage.TParagraph.Select;
var
P: ^TParagraph;
begin
P := @Self;
FOwner.FSelected := P^;
end;
Run Code Online (Sandbox Code Playgroud)