分成大会

时间:2018-04-01 10:35:07

标签: c assembly nasm x86-64

我正在尝试使用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 

2 个答案:

答案 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);

然后,参数在ediesi中传递(即32位有符号整数),寄存器rdirsi的上半部分未定义

ediesi分配给raxrbx以执行64位签名<时,需要

签名扩展 / em>除法(用于执行64位无符号除法零扩展)。

即代替:

mov   rax, rdi       
mov   rbx, rsi 

movsxedi上使用符号扩展来源的说明esi

movsx rax, edi
movsx rbx, esi

使用64位除法的真64位操作数

之前的方法是在&#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);

这样,参数将传递给rdirsi(即已经是64位有符号整数),并且将对真正的64位整数执行64位除法。 / p>

使用32位除法

如果它符合您的需要,您可能还需要考虑使用32位idiv,因为它的执行速度比64位快,并且生成的代码大小更小(没有REX前缀):

...   
mov eax, edi      
mov ebx, esi      
cdq
idiv ebx
...

_div的原型将是:

int32_t _div(int32_t, int32_t);