assembly x86程序集abs()实现?

bwleehnv  于 2023-03-30  发布在  其他
关注(0)|答案(9)|浏览(197)

x86汇编语言中有abs()函数吗?
(This这个问题最初提到的是得到2个有符号整数的差,但是如果你需要在减法中不可能发生有符号溢出的情况下这样做,那实际上是一个单独的问题。否则,是的,只是abs(x-y),可能首先扩大输入。)

cxfofazt

cxfofazt1#

这就是C库函数abs()在汇编中不分支的方式:

abs(x) = (x XOR y) - y

其中y = x >> 31(假设输入为32位),>>是算术右移运算符。

**上述公式的解释:**我们只想生成2的负数x的补数。

y = 0xFFFFFFFF, if x is negative
    0x00000000, if x is positive

因此,当x为正时,x XOR 0x00000000等于x。当x为负时,x XOR 0xFFFFFFFF等于x的1的补码。现在我们只需要将1相加,得到它的2的补码,这就是表达式-y所做的。因为0xFFFFFFFF在十进制中是-1。

让我们看看gcc(我的机器上是4.6.3)为以下代码生成的汇编:

C代码:

main()
{
  int x;
  int output = abs(x);
}

gcc 4.6.3生成的汇编代码段(AT&T语法),附带我的注解:

movl  -8(%rbp), %eax    # -8(%rbp) is memory for x on stack
  sarl  $31, %eax         #  shift arithmetic right: x >> 31, eax now represents y
  movl  %eax, %edx        #  
  xorl  -8(%rbp), %edx    #  %edx = x XOR y
  movl  %edx, -4(%rbp)    # -4(%rbp) is memory for output on stack
  subl  %eax, -4(%rbp)    # (x XOR y) - y

**奖金(来自Hacker's Delight):**如果你有一个快速乘以+1和-1,以下将为你提供32位xabs(x)

((x >> 30) | 1) * x
xienkqul

xienkqul2#

老线索,但如果我在这里冲浪晚了,你可能也有... abs是一个很好的例子,所以这应该在这里。

; abs(eax), with no branches.
; intel syntax (dest, src)

mov ebx, eax ;store eax in ebx
neg eax
cmovl eax, ebx ;if eax is now negative, restore its saved value
eaf3rand

eaf3rand3#

如果它是x86汇编,根据曾经有用的维基百科,下面应该可以工作。从另一个值中减去一个值,然后对结果使用这些指令:

cdq
xor eax, edx
sub eax, edx
sc4hvdpw

sc4hvdpw4#

如果你想正确处理所有的情况,你不能只做减法,然后取绝对值。你会遇到麻烦,因为两个有符号整数的差不一定能表示为一个有符号整数。例如,假设你使用的是32位2s补码整数,你想找出INT_MAX0x7fffffff)和INT_MIN0x80000000)之间的差。相减得到:

0x7fffffff - 0x80000000 = 0xffffffff

-1;当你取绝对值时,你得到的结果是1,而这两个数字之间的实际差值是0xffffffff,被解释为一个无符号整数(UINT_MAX)。
两个有符号整数之间的差 * 总是可以表示为无符号整数。要得到这个值(使用2s补码硬件),只需从较大的输入中减去较小的输入,并将结果解释为无符号整数;不需要绝对值。
下面是在x86上执行此操作的一种(多种方法中的一种,但不一定是最好的)方法,假设两个整数在eaxedx中:

cmp   eax,  edx  // compare the two numbers
    jge   1f
    xchg  eax,  edx  // if eax < edx, swap them so the bigger number is in eax
1:  sub   eax,  edx  // subtract to get the difference
8qgya5xd

8qgya5xd5#

假设整数在MMX或XMM寄存器中,使用psubd计算差值,然后使用pabsd获得差值的绝对值。
如果你的整数在普通的“普通”寄存器中,那么做减法,然后cdq技巧来获得绝对值。这需要使用一些特定的寄存器(cdq符号扩展eaxedx,不使用其他寄存器),所以你可能想用其他操作码来做事情。例如:

mov  r2, r1
sar  r2, 31

在寄存器r2中计算r1的符号扩展(如果r1为正或零,则为0,如果r1为负,则为0xFFFFFFFF)。这适用于所有32位寄存器r1r2,并取代cdq指令。

gmxoilav

gmxoilav6#

一个简短但直接的方法,使用条件移动指令(我认为奔腾和更高版本可用):

; compute ABS(r1-r2) in eax, overwrites r2
mov eax, r1
sub eax, r2
sub r2, r1
cmovg eax, r2

sub指令设置的标志与cmp指令相同。

8aqjt8rx

8aqjt8rx7#

ABS(EAX)

test   eax, eax   ;  Triger EFLAGS [CF, OF, PF, SF, and ZF]
  jns AbsResult     ;  If (SF) is off, jmp AbsResult
  neg    eax        ;  If (SF) is on. (negation nullify by this opcode)
AbsResult:

如果在eax中生成值的东西已经设置了标志,那么就不需要test了。如果输入值随机分布在正负之间,分支预测错误会使这个过程变慢。
这对RAX、AX、AL的工作方式相同。

r1zk6ea1

r1zk6ea18#

一次8个字符

inline u64 abs_8(u64 x)
{
    u64 y=(x>>7)&0x0101010101010101ull;
    return (x^((y<<8)-y))+y;
}
ddrv8njm

ddrv8njm9#

如果您要执行A-B. HTH,则有SUB指令

相关问题