[英]ASM/Delphi - Divide
I am trying to divide two numbers 50 and 5. This is my code: 我试图划分两个数字50和5.这是我的代码:
function Divide(Num1, Num2: Integer): Integer;
asm
MOV EAX, Num1
CDQ
MOV ECX, Num2
IDIV ECX
MOV @RESULT, ECX
end;
It gives me a DivisionByZeroException
exception in Delphi. 它在Delphi中给了我一个
DivisionByZeroException
异常。 Can someone tell me what am I doing wrong ? 有人能告诉我我做错了什么吗?
It's the CDQ
instruction. 这是
CDQ
指令。 From an online reference : 来自在线参考 :
Converts signed DWORD in EAX to a signed quad word in EDX:EAX by extending the high order bit of EAX throughout EDX
通过在EDX中扩展EAX的高位,将EAX中的带符号DWORD转换为EDX:EAX中的带符号四字
The problem is, Num2
being the second parameter, is stored in EDX, and since you're running CDQ
before loading EDX to ECX, what ends up in ECX is 0. Rewrite it like this, and your routine works as expected: 问题是,
Num2
是第二个参数,存储在EDX中,并且由于您在将EDX加载到ECX之前运行CDQ
,因此在ECX中结束的是0.重写它,并且您的例程按预期工作:
function Divide(Num1, Num2: integer): integer;
asm
MOV EAX, Num1
MOV ECX, Num2
CDQ
IDIV ECX
MOV @Result, EAX
end;
Mason's answer is accurate and clearly explains the error due to CDQ sign extending overwriting the input parameter in EDX. 梅森的答案是准确的,并清楚地解释了由于CDQ符号扩展覆盖EDX中的输入参数而导致的错误。 No need for me to say more, Mason got it spot on.
我不需要多说,梅森明白了。 And note the correction that IDIV returns the quotient in EAX rather than ECX.
并注意IDIV在EAX而不是ECX中返回商的修正。
I would like to try to offer some more general advice on writing asm. 我想尝试提供一些关于写作asm的更一般的建议。 I believe that your fundamental problem here is the use of the parameter names in your asm, rather than register names.
我相信你的根本问题是在你的asm中使用参数名,而不是寄存器名。
Since you use a register calling convention, it really pays to be explicit about the fact that parameters arrive in registers. 由于您使用寄存器调用约定,因此明确指出参数到达寄存器这一事实确实是值得的。 Had you done that it might have been clearer what was happening.
如果你这样做,可能会更清楚发生了什么。 Trying to use variable names gives you an illusion of abstraction.
尝试使用变量名称会给你一种抽象的幻觉。 In reality that abstraction is not there.
实际上,抽象不存在。 By hiding the register parameter passing from view you make it hard to spot such errors, and sure enough you stomped on your input!
通过隐藏从视图传递的寄存器参数,您很难发现这样的错误,并且确定您踩到了输入!
First of all let's write the code in Mason's answer in terms of registers. 首先让我们在寄存器方面用Mason的答案编写代码。 Include comments for added clarity.
添加评论以增加清晰度。 Like this:
像这样:
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;
Right away we get an immediate benefit that the first and last lines are blatantly pointless. 我们马上获得了直接好处,即第一行和最后一行显然毫无意义。 You could not see that in your version because of the use of variable names.
由于使用了变量名,您无法在您的版本中看到它。
So we can write it like this: 所以我们可以像这样写:
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
Of course it is no coincidence that most arithmetic operations return the result in EAX, and that same register is used for function return values. 当然,大多数算术运算在EAX中返回结果并且相同的寄存器用于函数返回值并非巧合。
The point is that writing asm is all about understanding register use, and re-use. 关键是写asm就是了解寄存器的使用和重用。 Don't obscure that with variable names.
不要用变量名来掩盖它。 Keep register use front and centre, in plain sight.
保持注册使用的正面和中心,在视线中。 Once you start doing so, you'll not make hard to spot errors like in the question, and you'll be able to remove spurious operations when values happen to land in the right registers.
一旦你开始这样做,你就不会很难发现问题中的错误,并且当价值恰好落在正确的寄存器中时你将能够删除虚假操作。
My advice is never to use parameter names, or Result
in asm code. 我的建议是永远不要使用参数名称或
Result
asm代码。
The other very obvious point is that you are re-implementing the div
operator. 另一个非常明显的一点是,您正在重新实现
div
运算符。 By placing this in an asm function you are inevitably making the code less efficient, and less readable. 通过将其置于asm函数中,您不可避免地使代码效率降低,可读性降低。
For what it is worth, this particular function can actually be written more efficiently as Pascal. 对于它的价值,这个特定的功能实际上可以像Pascal一样更有效地编写。 Consider the following program:
考虑以下程序:
{$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.
Now, DividePas
is worse than DivideAsm
. 现在,
DividePas
比DivideAsm
差。 The former is compiled, with optimisation, to: 前者通过优化编译为:
0040524C 53 push 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 pop ebx 00405257 C3 ret
Clearly DivideAsm
wins by dint of skipping the prolog/epilog. 很明显
DivideAsm
因跳过prolog / DivideAsm
获胜。
But let's look at the main body of the code: 但是让我们看一下代码的主体:
SO22570866.dpr.28: i := 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 99 cdq 004060E6 F7FF idiv edi 004060E8 03D8 add ebx,eax SO22570866.dpr.32: inc(l, DivideAsm(i, j)); 004060EA 8BD7 mov edx,edi 004060EC 8BC6 mov eax,esi 004060EE E851F1FFFF call DivideAsm 004060F3 03D8 add 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 add ebx,eax SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406100 8BC6 mov eax,esi 00406102 99 cdq 00406103 F7FF idiv edi 00406105 03D8 add ebx,eax
You can see that the compiler has more freedom of register use with the inline version. 您可以看到编译器可以更自由地使用内联版本进行寄存器使用。 The compiler is not tied to the calling convention ABI.
编译器不依赖于调用约定ABI。 That allows it to emit fewer
MOV
operations. 这允许它发出更少的
MOV
操作。 In fact, the interaction between the inline engine and the optimiser is very good. 实际上,内联引擎和优化器之间的交互非常好。 Here's the first version of the code that I wrote:
这是我写的代码的第一个版本:
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, i div j);
inc(l, DividePasInline(i, j));
But the optimiser defeats me on the last two statements: 但优化者在最后两个声明中击败了我:
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 add ebx,ecx SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406102 03D9 add ebx,ecx
The optimiser is able to recognize that the ECX
register already contains the result of DividePasInline
and skips the code altogether! 优化器能够识别出
ECX
寄存器已经包含DividePasInline
的结果并完全跳过代码!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.