除以负数让我在 NASM 中溢出

Zei*_*nes 2 assembly x86-64 nasm

我正在自学一些使用 x86-64 Mac OS 的汇编编程。我试图弄清楚为什么在将正整数除以负整数时会溢出。例如,5/-2必须返回-2。但是,就我而言,它2147483371在我执行时返回 a-554/2而不是-277...这是我的程序集文件中的内容:

; compiling using: nasm -f macho64 -o divide.o divide.s
[bits 64]
global _divide
section .text

; int divide(int dividend, int divisor)
_divide:

    xor rdx, rdx        ; making this to 0
    push rbp            ; base stack pointer
    mov rax, rdi        ; dividend
    mov rcx, rsi        ; divisor
    idiv rcx            ; integer division

    add rsp, 8
    ret
Run Code Online (Sandbox Code Playgroud)

在我的main.c文件中,我有这个:

#include <stdio.h>
extern int divide(int dividend, int divisor);
int main(void)
{
    printf("divide: %d\n\n", divide(-554,2));
    return (0);
}
Run Code Online (Sandbox Code Playgroud)

输出divide: 2147483371

有人可以向我解释我到底做错了什么吗?

pax*_*blo 5

32 位值相当于其中的一半确实是您得到的答案。所以它看起来像是一个签名/未签名的问题。并且,在检查x86 文档时,我们看到:-554signed4,294,966,742unsigned 2,147,483,371idiv

IDIV r/m64 Signed divide RDX:RAX by r/m64, result stored in:
    RAX <- Quotient,
    RDX <- Remainder.
Run Code Online (Sandbox Code Playgroud)

请注意第一行,特别是“有符号除以rdx:rax by”位。当英特尔谈论 时rdx:rax,它们的意思是由这两个 64 位寄存器形成的 128 位值。假设这两个 64 位寄存器包含(十六进制)值:

rax : 01234567 89ABCDEF
rdx : 11112222 FFFFEEEE
Run Code Online (Sandbox Code Playgroud)

那么该rdx:rax值将是 128 位值:

rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF
Run Code Online (Sandbox Code Playgroud)

现在,因为您正在清零rdx,所以组合值被视为正值,因为最高位为零。您实际需要做的是sign-extend rax into rdx:rax,这是一种在扩展值中保留符号的方法。例如,考虑将 32 位-1正确和不正确地符号扩展为 64 位值:

         ffffffff     32-bit:                        -1.
ffffffff ffffffff     64-bit proper:                 -1.
00000000 ffffffff     64-bit improper:    4,294,967,295.
Run Code Online (Sandbox Code Playgroud)

为了正确进行符号扩展rdx如果最右边的位(rax对您而言)形成负数则最左边的位(在您的情况下)应全部为一位,否则为所有零位。

当然,那些聪明的英特尔工程师已经想到了这个用例,所以你可以用cqo convert-quadword-to-octoword正确扩展的指令来做。考虑到这一点,您的设置代码eax将变为:

    mov   rax, rdi          ; Get dividend and
    cqo                     ;   sign extend to rdx:rax.
Run Code Online (Sandbox Code Playgroud)

但是,您可能还有一个额外的问题。即使 System V x86-64 ABI 指定在 64 位寄存器 ( rXX)中传递参数,但传递 32 位值实际上可能会使高位包含垃圾(我认为您可以离开返回值的上部也是垃圾。有关详细信息,请参阅此优秀答案

所以,你应该不会认为你将在整个64位寄存器有一个理智的价值,只有在最右边32位。

在您的情况下(假设是 32 位整数),您应该符号扩展 32 到 64 而不是 64 到 128,并使用较小宽度的除法指令。这将导致更像:

global _divide
section .text

; int32_t divide(int32_t ediDividend, int32_t esiDivisor)
_divide:
    mov   eax, edi          ; Get 32-bit dividend and
    cdq                     ;   sign extend to 64-bit edx:eax.

    idiv  esi               ; Weave magic here,
                            ;   zeros leftmost rax.

    ret                     ; Return quotient in rax/eax.
Run Code Online (Sandbox Code Playgroud)

这是未经测试的,但应该做你想做的。我实际上已经删除了推送,rbp因为我很确定它没有必要。它似乎没有被破坏(这个函数既没有改变它,也没有调用任何其他可能改变它的函数),而且看起来你从来没有在你的原始代码中正确地恢复过它。


归档时间:

查看次数:

1264 次

最近记录:

7 年,2 月 前