50 と 5 の 2 つの数値を除算しようとしています。これが私のコードです。
function Divide(Num1, Num2: Integer): Integer;
asm
MOV EAX, Num1
CDQ
MOV ECX, Num2
IDIV ECX
MOV @RESULT, ECX
end;
DivisionByZeroException
Delphiでは例外です。誰かが私が間違っていることを教えてもらえますか?
メイソンの答えは正確であり、EDX の入力パラメーターを上書きする CDQ 符号拡張によるエラーを明確に説明しています。私がこれ以上言う必要はありません、メイソンはそれを正確に理解しました。また、IDIV が ECX ではなく EAX で商を返すという修正に注意してください。
asm を書く上で、もう少し一般的なアドバイスを提供したいと思います。ここでの基本的な問題は、名前を登録するのではなく、asm でパラメーター名を使用することだと思います。
レジスタの呼び出し規則を使用しているため、パラメータがレジスタに到着するという事実を明示することは本当に有益です。そうすれば、何が起こっているのかがより明確になったかもしれません。変数名を使用しようとすると、抽象化の錯覚が生じます。実際には、その抽象化は存在しません。レジスターパラメーターの受け渡しをビューから隠すことで、そのようなエラーを見つけるのが難しくなり、入力を踏みにじったことになります!
まず第一に、Mason の回答のコードをレジスタに関して書きましょう。明確にするためにコメントを含めます。このような:
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV EAX, EAX
MOV ECX, EDX
CDQ
IDIV ECX
MOV EAX, EAX
end;
すぐに、最初と最後の行が明らかに無意味であるという利点がすぐに得られます。変数名を使用しているため、あなたのバージョンではそれを見ることができませんでした。
したがって、次のように記述できます。
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
もちろん、ほとんどの算術演算が EAX で結果を返し、その同じレジスタが関数の戻り値に使用されるのは偶然ではありません。
ポイントは、asm を書くことは、レジスターの使用と再利用を理解することです。変数名でそれを曖昧にしないでください。見やすい場所で、レジスターの使用を正面と中央に保ちます。これを開始すると、質問のようにエラーを見つけるのが難しくならず、値がたまたま正しいレジスタに到達したときに誤った操作を削除できるようになります。
私のアドバイスは、パラメーター名を使用したりResult
、asm コードで使用したりしないことです。
div
もう 1 つの非常に明白な点は、オペレーターを再実装していることです。これを asm 関数に配置すると、必然的にコードの効率が低下し、読みにくくなります。
それだけの価値があるため、この特定の関数は、実際には Pascal としてより効率的に記述できます。次のプログラムを検討してください。
{$APPTYPE CONSOLE}
function DivideAsm(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
function DividePas(Num1, Num2: integer): integer;
begin
Result := Num1 div Num2;
end;
function DividePasInline(Num1, Num2: integer): integer; inline;
begin
Result := Num1 div Num2;
end;
var
i, j, k, l: Integer;
begin
i := 666;
j := 42;
l := 0;
inc(l, i div j);
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, DividePasInline(i, j));
Writeln(l);
end.
今、DividePas
より悪いですDivideAsm
。前者は、最適化されて次のようにコンパイルされます。
0040524C 53 プッシュ ebx 0040524D 8BDA mov ebx,edx 0040524F 8BC8 mov ecx,eax 00405251 8BC1 mov eax,ecx 00405253 99 cdq 00405254 F7FB idiv ebx 00405256 5B ポップ ebx 00405257 C3 ret
DivideAsm
プロローグ/エピローグをスキップすることで明らかに勝ちます。
しかし、コードの本体を見てみましょう。
SO22570866.dpr.28: 私:= 666; 004060D7 BE9A020000 mov esi,$0000029a SO22570866.dpr.29: j := 42; 004060DC BF2A000000 mov edi,$0000002a SO22570866.dpr.30: l := 0; 004060E1 33DB xor ebx,ebx SO22570866.dpr.31: inc(l, i div j); 004060E3 8BC6 mov eax,esi 004060E5 99cdq 004060E6 F7FF idiv edi 004060E8 03D8 追加 ebx,eax SO22570866.dpr.32: inc(l, DivideAsm(i, j)); 004060EA 8BD7 mov edx,edi 004060EC 8BC6 mov eax,esi 004060EE E851F1FFFF コール DivideAsm 004060F3 03D8 ebx、eax を追加 SO22570866.dpr.33: inc(l, DividePas(i, j)); 004060F5 8BD7 mov edx,edi 004060F7 8BC6 mov eax,esi 004060F9 E84EF1FFFF call DividePas 004060FE 03D8 ebx、eax を追加 SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406100 8BC6 mov eax,esi 00406102 99 cdq 00406103 F7FF idiv edi 00406105 03D8 追加 ebx,eax
コンパイラは、インライン版の方がレジスタの使用の自由度が高いことがわかります。コンパイラは、呼び出し規約 ABI に関連付けられていません。これにより、より少ないMOV
操作を発行できます。実際、インライン エンジンとオプティマイザの間の相互作用は非常に良好です。私が書いたコードの最初のバージョンは次のとおりです。
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, i div j);
inc(l, DividePasInline(i, j));
しかし、オプティマイザーは最後の 2 つのステートメントで私を打ち負かします。
SO22570866.dpr.33: inc(l, i div j); 004060F9 8BC6 mov eax,esi 004060FB 99 cdq 004060FC F7FF idiv edi 004060FE 8BC8 mov ecx,eax 00406100 03D9 追加 ebx、ecx SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406102 03D9 追加 ebx、ecx
ECX
オプティマイザーは、レジスターに の結果が既に含まれていることを認識しDividePasInline
、コードを完全にスキップします!