9

「Using Assembler in Delphi」によると、eaxが含まれますSelf。ただし、 の内容はeax、示されているように 0 です。何が悪いのだろうか?

procedure TForm1.FormCreate(Sender: TObject);
var
  X, Y: Pointer;
begin
  asm
    mov X, eax
    mov Y, edx
  end;
  ShowMessage(IntToStr(NativeInt(X)) + ' ; ' + IntToStr(NativeInt(Y)));
end;
4

2 に答える 2

13

デバッグ設定でこれをコンパイルすると生成されるコードは次のようになります。

  始める
005A9414 55プッシュebp
005A9415 8BEC mov ebp,esp
005A9417 83C4E4 追加 esp、-$1c
005A941A 33C9 xor ecx,ecx
005A941C 894DEC mov [ebp-$14],ecx
005A941F 894DE8 mov [ebp-$18],ecx
005A9422 894DE4 mov [ebp-$1c],ecx
005A9425 8955F0 mov [ebp-$10],edx
005A9428 8945F4 mov [ebp-$0c],eax
005A942B 33C0 xor eax,eax
005A942D 55プッシュebp
005A942E 6890945A00 プッシュ $005a9490
005A9433 64FF30 プッシュ dword ptr fs:[eax]
005A9436 648920 mov fs:[eax],esp
  mov X, eax
005A9439 8945FC mov [ebp-$04],eax
  移動 Y、edx
005A943C 8955F8 mov [ebp-$08],edx

コードの実行が開始されると、eax実際には自己ポインターになります。しかし、コンパイラはそれをに保存してからゼロ化することを選択しましebp-$0ceax。それは本当にコンパイラ次第です。

リリース設定の下のコードは非常に似ています。コンパイラは依然としてゼロ化を選択しますeax。もちろん、コンパイラがそれを行うことに依存することはできません。

  始める
005A82A4 55プッシュebp
005A82A5 8BEC mov ebp,esp
005A82A7 33C9 xor 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 プッシュ dword ptr fs:[eax]
005A82B9 648920 mov fs:[eax],esp
  mov X, eax
005A82BC 8945FC mov [ebp-$04],eax
  移動 Y、edx
005A82BF 8955F8 mov [ebp-$08],edx

パラメータの受け渡しは、関数の実行開始時のレジスタとスタックの状態を定義することに注意してください。次に何が起こるか、関数がパラメーターをどのようにデコードするかは、コンパイラー次第です。パラメータの受け渡しに使用されたレジスタとスタックをそのままにしておく義務はありません。

eaxasm を関数の途中に挿入すると、揮発性レジスタが特定の値を持つことを期待できなくなります。それらは、コンパイラーがたまたま最近それらに入れたものを保持します。

関数の実行の最初にレジスターを調べたい場合は、純粋な 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 mov ebp,esp
005A829B 51プッシュecx
  mov X, eax
005A829C 8945FC mov [ebp-$04],eax
  結果 := TForm4(X).i + j;
005A829F 8B45FC mov eax,[ebp-$04]
005A82A2 8B80A0030000 mov eax,[eax+$000003a0]
005A82A8 03C2 追加 eax,edx
  終わり;
005A82AA 59 ポップ ecx
005A82AB 5D ポップ ebp
005A82AC C3 ret

コードを実行すると、フォームのキャプションが期待値に変更されます。


正直なところ、Pascal 関数内の asm ブロックとして配置されたインライン アセンブリはあまり役に立ちません。アセンブリを記述することについては、レジスタとスタックの状態を完全に理解する必要があるということです。これは、ABI によって定義された、関数の最初と最後で明確に定義されています。

しかし、関数の途中では、その状態はコンパイラによる決定に完全に依存します。そこに asm ブロックを挿入するには、コンパイラが行った決定を知る必要があります。また、コンパイラがユーザーの決定を理解できないことも意味します。これは通常非現実的です。実際、x64 コンパイラの場合、Embarcadero はそのようなインライン asm ブロックを禁止しました。私は個人的に、自分のコードでインライン asm ブロックを使用したことがありません。私が asm を書く場合は、常に純粋な asm 関数を書きます。

于 2014-04-17T09:09:03.710 に答える