我遇到了 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;
邏輯是我的頁面可以包含多個段落,并且在某些時候我希望這些段落之一被選中(以便能夠在我的程式的其他部分對其進行一些操作):
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
我可以看到三個正確的 movs,它們將記憶體從本地記錄復制到類`。
但!如果我向我的記錄中添加更多欄位,生成的 asm 代碼會更改并且記錄分配不再正確執行。
TParagraph = record
public
FOwner: TPage;
FFirst: Integer;
FSecond: Integer;
FThird: Integer;
procedure Select;
end;
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
然后我在課堂記錄 FSelected 中得到了垃圾:

在 lea 指令之后,CPU 狀態是這樣的:

In this example 02D37280 is the address of my lcPage class, so 02D37284 should contain start of its field - FSelected record. But movsd instruction copies memory from ESI to EDI, from 02D37280 to 02D37284, which make absolutly no sense!
If i change ESI register to value of EAX (19F308), which is start of my local lcParagraph variable, the copy is performed properply:

Is what i described is known bug? Or am i missing something fundamental about delphi? Is this a good way to assign records? I can easily workaround the problem, for example, by changing FOwner.FSelected:=Self; to CopyMemory(@FOwner.FSelected, @Self, SizeOf(Self)); in procedure TPage.TParagraph.Select;. But i want to figure out what is wrong.
Minimal reproducible example:
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.
uj5u.com熱心網友回復:
這是一個仍然存在于 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.
或者另一個非常簡單的解決方法是引入一個額外的區域指標變數來保存指向的指標Self:
procedure TPage.TParagraph.Select;
var
P: ^TParagraph;
begin
P := @Self;
FOwner.FSelected := P^;
end;
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/353708.html
標籤:delphi delphi-xe3
