我正在自学一些使用 x86-64 Mac OS 的汇编编程。我试图找出为什么当将正整数除以负整数时会出现溢出。例如,5/-2
必须返回 -2
。但是,就我而言,当我执行 -554/2
而不是 -277
时,它会返回 2147483371
...这就是我所拥有的在我的汇编文件中:
; 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
在我的 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);
}
输出:除以:2147483371
有人可以向我解释一下我到底做错了什么吗?
最佳答案
32 位值 -554<sub>signed</sub>
相当于 4,294,966,742<sub>unsigned</sub>
,其中一半确实 2,147,483,371
,即您得到的答案。所以它看起来像是一个签名/未签名的问题。并且,在检查 the x86 docs for idiv
后,我们看到:
IDIV r/m64 Signed divide RDX:RAX by r/m64, result stored in:
RAX <- Quotient,
RDX <- Remainder.
请注意第一行,特别是“有符号除rdx:rax”位。当 Intel 谈论 rdx:rax
时,它们指的是由这两个 64 位寄存器形成的 128 位值。假设这两个 64 位寄存器包含(十六进制)值:
rax : 01234567 89ABCDEF
rdx : 11112222 FFFFEEEE
那么 rdx:rax
值将是 128 位值:
rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF
现在,因为您将 rdx
清零,所以组合值被认为正,因为最高位为零。您实际上需要做的是将rax
符号扩展到rdx:rax
中,这是一种在扩展值中保留符号的方法。例如,考虑 32 位 -1
,正确和不正确地符号扩展为 64 位值:
ffffffff 32-bit: -1.
ffffffff ffffffff 64-bit proper: -1.
00000000 ffffffff 64-bit improper: 4,294,967,295.
要正确地进行符号扩展,如果最右边的位(在您的情况下为 rdx
)形成负数,则最左边的位(在您的情况下为 rax
)应该全部为 1 位,否则全部为 0 位.
当然,那些聪明的英特尔工程师已经想到了这个用例,因此您可以使用 cqo
convert-quadword-to-octoword
指令来实现,该指令的符号可以正确扩展。考虑到这一点,设置 eax
的代码将变为:
mov rax, rdi ; Get dividend and
cqo ; sign extend to rdx:rax.
<小时/>
但是,您可能还会遇到额外问题。尽管 System V x86-64 ABI 指定参数在 64 位寄存器 ( rXX
) 中传递,但传递 32 位值实际上可能会留下包含垃圾的高位(我认为您可以在返回值的上部也留下垃圾。有关详细信息,请参阅 this excellent answer。
因此,您不应该假设整个 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.
这尚未经过测试,但应该可以满足您的要求。我实际上已经删除了 rbp
的推送,因为我很确定这是没有必要的。它似乎没有被损坏(这个函数既没有改变它,也没有调用任何其他可以改变它的函数),并且看起来你实际上从未在原始代码中正确恢复它。
关于assembly - 除以负数会导致 NASM 溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51717317/