除以负数会使我在NASM中产生溢出 [英] Dividing with a negative number gives me an overflow in NASM
问题描述
我正在自学一些使用x86-64 Mac OS的汇编程序.我试图弄清楚为什么要将正整数与负整数相除会给我带来溢出.例如,5/-2
必须返回-2
.但是,就我而言,当我执行-554/2
而不是-277
时,它将返回2147483371
.这就是我的汇编文件中的内容:
I'm teaching myself some assembly programming with x86-64 Mac OS. I'm trying to figure out why when it comes to dividing a positive integer with a negative integer gives me an overflow. For example, 5/-2
must return -2
. However, in my case, it returns a 2147483371
when I do -554/2
instead of -277
... This is what I have in my assembly file:
; 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
文件中,我有这个:
In my main.c
file, I have this:
#include <stdio.h>
extern int divide(int dividend, int divisor);
int main(void)
{
printf("divide: %d\n\n", divide(-554,2));
return (0);
}
输出:divide: 2147483371
有人可以向我解释我到底在做什么错吗?
Can someone explain to me what exactly I'm doing wrong?
推荐答案
32位值-554signed
等效于4,294,966,742unsigned
,而一半的值实际上是 ,答案是你得到了.因此,它看起来像一个已签名/未签名的问题.并且,在检查 idiv
的x86文档后,我们看到:
The 32-bit value -554signed
is equivalent to 4,294,966,742unsigned
and half of that is indeed 2,147,483,371
, the answer you're getting. So it looks like a signed/unsigned issue. And, upon examining the x86 docs for idiv
, we see:
IDIV r/m64 Signed divide RDX:RAX by r/m64, result stored in:
RAX <- Quotient,
RDX <- Remainder.
请注意,第一行是有符号除以 rdx:rax 除"位.当英特尔谈论rdx:rax
时,它们表示由这两个64位寄存器形成的128位值.假设这两个64位寄存器包含(十六进制)值:
Note that first line, specifically the "signed divide rdx:rax by" bit. When Intel talk about rdx:rax
, they mean the 128-bit value formed from those two 64-bit registers. Assuming that those two 64-bit registers contain the (hex) values:
rax : 01234567 89ABCDEF
rdx : 11112222 FFFFEEEE
然后rdx:rax
值将是128位值:
then the rdx:rax
value will be the 128-bit value:
rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF
现在,由于您要清零rdx
,因此合并的值被视为正,因为最高位为零.实际上, 要做的是将 sign-extend rax
转换为rdx:rax
,这是一种将符号保留在扩展值中的方法.例如,考虑32位-1
,正确且不正确地将其符号扩展为64位值:
Now, because you are zeroing out rdx
, the combined value is considered positive because the top bit is zero. What you actually need to do is sign-extend rax
into rdx:rax
, a method that preserves the sign in the extended value. For example, consider the 32-bit -1
, sign-extended properly and improperly into a 64-bit value:
ffffffff 32-bit: -1.
ffffffff ffffffff 64-bit proper: -1.
00000000 ffffffff 64-bit improper: 4,294,967,295.
要正确地对进行符号扩展,如果最右边的位(对您来说为rax
)形成负数,则最左边的位(在您的情况下为rdx
)应全部为一位.否则为零.
To sign-extend properly, the leftmost bits (rdx
in your case) should be all one-bits if the rightmost bits (rax
for you) form a negative number, all zero-bits otherwise.
当然,那些聪明的Intel工程师已经已经想到了这种用例,因此您可以使用cqo
convert-quadword-to-octoword
指令来做到这一点,该指令可以正确扩展.考虑到这一点,您用于设置eax
的代码将变为:
Of course, those clever Intel engineers have already thought of just this use case, so you can do it with the cqo
convert-quadword-to-octoword
instruction, which sign extends correctly. With that in mind, your code for setting eax
would become:
mov rax, rdi ; Get dividend and
cqo ; sign extend to rdx:rax.
但是,您可能还会遇到 extra 问题.即使System V x86-64 ABI指定将参数传递到64位寄存器(rXX
)中,传递32位值也可能实际上会留下包含垃圾的高位(我认为您是也允许在返回值的顶部留下垃圾.请参见
However, you may have an extra problem as well. Even though the System V x86-64 ABI specifies that the parameters are passed in the 64-bit registers (rXX
), it's possible that passing 32-bit values will actually leave the upper bits containing rubbish (and I think you're allowed to leave rubbish in the upper parts of the return value as well. See this excellent answer for details.
因此,您应该不假定整个64位寄存器中只有最右边的32位具有合理的值.
So you should not assume that you will have a sane value in the entire 64-bit register, only in the rightmost 32 bits.
在您的情况下(假定为32位整数),应使用较小宽度的除法指令对32到64而不是64到128进行符号扩展.这将导致更像:
In your case (assuming 32-bit integers), you should be sign extending 32-to-64 rather than 64-to-128, and using a smaller-width division instruction. That would result in something more like:
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
的推动,因为我敢肯定这是没有必要的.它似乎没有被破坏(此函数既不会对其进行更改,也不会调用任何其他可以更改它的函数),并且看来您实际上从来没有在原始代码中正确地对其进行过恢复.
This is untested, but should do what you want. I've actually removed the pushing of rbp
since I'm pretty certain it's not necessary. It appears not to be corrupted (this function neither changes it, nor calls any other function which could change it), and it appears you never actually restored it properly in your original code anyway.
这篇关于除以负数会使我在NASM中产生溢出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!