我正在尝试使用Linux命令dc
在C语言中定义一个计算器,程序的结构并不那么重要,你需要知道我得到两个数字,我想在输入时将它们分开/
。因此,我将这两个数字发送到进行除法的汇编函数(参见下面的代码)。但这仅适用于正数。
当输入999 3 /
时,它返回333,这是正确的,但是当键入-999 3 /
时,我得到奇怪的数字1431655432,并且当输入两个负数时,如-999 -3 /
我每次都得到0两个负数。
汇编中的代码是:
section .text
global _div
_div:
push rbp ; Save caller state
mov rbp, rsp
mov rax, rdi ; Copy function args to registers: leftmost...
mov rbx, rsi ; Next argument...
cqo
idiv rbx ; divide 2 arguments
mov [rbp-8], rax
pop rbp ; Restore caller state
答案 0 :(得分:6)
您的评论说您将整数传递给_idiv
。如果您使用int
,则这些是32位值:
extern int _div (int a, int b);
当传递给函数a
时,它将位于 RDI 的底部32位中,而b
将位于 RSI <的底部32位中/ em>的。参数的高32位可以是垃圾,但通常为零,but doesn't have to be the case。
如果使用64位寄存器作为IDIV的除数,则除法为RDX:RAX / 64位除数(在您的情况下为 RBX )。这里的问题是您使用完整的64位寄存器进行32位除法。如果我们假设 RDI 和 RSI 的高位最初为0,则 RSI 将为0x00000000FFFFFC19(RAX),RDI将为0x0000000000000003(RBX)。 CQO 将 RAX 扩展为 RDX 。 RAX 的最高位为零,因此 RDX 将为零。该部门看起来像:
0x000000000000000000000000FFFFFC19 / 0x0000000000000003 = 0x55555408
0x55555408恰好是1431655432(十进制),这是你看到的结果。一个解决方法是使用32位寄存器进行除法。要将 EAX ( RAX 的低32位)扩展到 EDX ,您可以使用CDQ代替 CQO < /em>。然后您可以通过 EBX 划分 EDX : EAX 。这应该是你正在寻找的32位签名部门。代码看起来像:
cdq
idiv ebx ; divide 2 arguments EDX:EAX by EBX
请注意 RBX , RBP , R12 到 R15 都需要通过您的功能保存你修改它们(它们是AMD 64-bit ABI中的易失性寄存器)。如果您修改 RBX ,则需要确保像使用 RBP 一样保存和恢复它。更好的选择是使用其中一个易失性寄存器,如 RCX 而不是 RBX 。
您不需要中间寄存器来将除数放入。您可以直接使用 RSI (或固定版本中的 ESI ),而不是将其移动到 RBX 等寄存器。
答案 1 :(得分:5)
您的问题与参数传递给_div
的方式有关。
假设您的_div
原型是:
int64_t _div(int32_t, int32_t);
然后,参数在edi
和esi
中传递(即32位有符号整数),寄存器rdi
和rsi
的上半部分未定义
edi
和esi
分配给rax
和rbx
以执行64位签名<时,需要签名扩展 / em>除法(用于执行64位无符号除法零扩展)。
即代替:
mov rax, rdi
mov rbx, rsi
在movsx
和edi
上使用符号扩展来源的说明esi
:
movsx rax, edi
movsx rbx, esi
之前的方法是在&#34;假&#34;上执行64位除法。 64位操作数(即符号扩展 32位操作数)。将64位指令与&#34; 32位操作数混合使用#34;通常不是一个好主意,因为它可能导致worse performance and larger code size。
更好的方法是简单地更改_div
函数的C原型以接受实际的64位参数,即:
int64_t _div(int64_t, int64_t);
这样,参数将传递给rdi
和rsi
(即已经是64位有符号整数),并且将对真正的64位整数执行64位除法。 / p>
如果它符合您的需要,您可能还需要考虑使用32位idiv
,因为它的执行速度比64位快,并且生成的代码大小更小(没有REX
前缀):
...
mov eax, edi
mov ebx, esi
cdq
idiv ebx
...
_div
的原型将是:
int32_t _div(int32_t, int32_t);