我在调试设置下编译它时生成的代码是这样的:
开始
005A9414 55推ebp
005A9415 8BEC 移动 ebp,esp
005A9417 83C4E4 添加 esp,-$1c
005A941A 33C9 异或 ecx,ecx
005A941C 894DEC mov [ebp-$14],ecx
005A941F 894DE8 mov [ebp-$18],ecx
005A9422 894DE4 mov [ebp-$1c],ecx
005A9425 8955F0 移动 [ebp-$10],edx
005A9428 8945F4 mov [ebp-$0c],eax
005A942B 33C0 xor eax,eax
005A942D 55推ebp
005A942E 6890945A00 推 $005a9490
005A9433 64FF30 推双字 ptr fs:[eax]
005A9436 648920 mov fs:[eax],esp
mov X, eax
005A9439 8945FC 移动 [ebp-$04],eax
mov Y, edx
005A943C 8955F8 mov [ebp-$08],edx
当代码开始执行时,eax
确实是自指针。但是编译器选择将其保存到ebp-$0c
然后 zeroise eax
。这真的取决于编译器。
发布设置下的代码非常相似。编译器仍然选择归零eax
。当然,你不能依赖编译器来做这件事。
开始
005A82A4 55推ebp
005A82A5 8BEC 移动 ebp,esp
005A82A7 33C9 异或 ecx,ecx
005A82A9 51 推 ecx
005A82AA 51 推 ecx
005A82AB 51 推 ecx
005A82AC 51 推 ecx
005A82AD 51 推 ecx
005A82AE 33C0 xor eax,eax
005A82B0 55推ebp
005A82B1 6813835A00 推 $005a8313
005A82B6 64FF30 推双字 ptr fs:[eax]
005A82B9 648920 mov fs:[eax],esp
mov X, eax
005A82BC 8945FC mov [ebp-$04],eax
mov Y, edx
005A82BF 8955F8 mov [ebp-$08],edx
请记住,参数传递定义了函数开始执行时寄存器和堆栈的状态。接下来会发生什么,函数如何解码参数取决于编译器。没有义务保留用于参数传递的寄存器和堆栈。
如果将 asm 注入函数的中间,则不能期望 volatile 寄存器喜欢eax
具有特定值。它们将保存编译器最近放入其中的任何内容。
如果要在函数执行开始时检查寄存器,则需要使用纯 asm 函数来确保避免编译器修改用于参数传递的寄存器:
var
X, Y: Pointer;
asm
mov X, eax
mov Y, edx
// .... do something with X and Y
end;
编译器的选择很大程度上取决于函数其余部分的代码。对于您的代码,组装要传递给的字符串的复杂性ShowMessage
会导致相当大的序言。请考虑以下代码:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
i: Integer;
function Sum(j: Integer): Integer;
end;
....
procedure TForm1.FormCreate(Sender: TObject);
begin
i := 624;
Caption := IntToStr(Sum(42));
end;
function TForm1.Sum(j: Integer): Integer;
var
X: Pointer;
begin
asm
mov X, eax
end;
Result := TForm1(X).i + j;
end;
在这种情况下,代码很简单,编译器可以不用eax
管它。优化的发布构建代码Sum
是:
开始
005A8298 55推ebp
005A8299 8BEC 移动 ebp,esp
005A829B 51 推 ecx
mov X, eax
005A829C 8945FC 移动 [ebp-$04],eax
结果 := TForm4(X).i + j;
005A829F 8B45FC 移动 eax,[ebp-$04]
005A82A2 8B80A0030000 移动 eax,[eax+$000003a0]
005A82A8 03C2 添加 eax,edx
结尾;
005A82AA 59 流行音乐
005A82AB 5D pop ebp
005A82AC C3 RET
当您运行代码时,表单的标题会更改为预期值。
老实说,作为 asm 块放置在 Pascal 函数中的内联汇编并不是很有用。编写汇编的事情是您需要完全了解寄存器和堆栈的状态。这在 ABI 定义的函数的开头和结尾处得到了很好的定义。
但是在函数的中间,该状态完全取决于编译器做出的决定。在其中注入 asm 块需要您知道编译器做出的决定。这也意味着编译器无法理解您做出的决定。这通常是不切实际的。事实上,对于 x64 编译器,Embarcadero 禁止了这种内联 asm 块。我个人从未在我的代码中使用过内联 asm 块。如果我写 asm,我总是写纯 asm 函数。