x86程序集abs()实现?

Gre*_* C. 20 x86 assembly

我需要得到2个有符号整数的区别.是否有x86汇编语言的ABS()函数,所以我可以这样做.任何帮助将不胜感激.

bit*_*its 22

这是C库函数abs()在没有分支的情况下在汇编中执行的方式:

   abs(x) = (x XOR y) - y
Run Code Online (Sandbox Code Playgroud)

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

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

y = 0xFFFF, if x is negative
    0x0000, if x is positive
Run Code Online (Sandbox Code Playgroud)

所以当x积极 x XOR 0x0000等于x.而当x负数 x XOR 0xFFFF等于1的补数时x.现在我们只需要添加1以得到它的2的补码,这就是表达式-y正在做的事情.因为0xFFFF十进制是-1.

让我们看一下为下面的代码生成的程序集gcc(在我的机器上为4.6.3):

C代码:

main()
{
  int x;
  int output = abs(x);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

奖金(来自Hacker's Delight):如果您快速乘以+1和-1,以下内容将为您提供abs(x):

      ((x >>> 30) | 1) * x
Run Code Online (Sandbox Code Playgroud)


Mar*_*ins 17

如果它是x86程序集,那么根据有用的维基百科应该可以使用以下内容.从另一个值中减去一个值,然后在结果上使用这些指令:

cdq
xor eax, edx
sub eax, edx
Run Code Online (Sandbox Code Playgroud)


Ste*_*non 14

如果要正确处理所有情况,则不能只减去然后取绝对值.您将遇到麻烦,因为两个有符号整数的差异不一定表示为有符号整数.例如,假设您使用32位2s补码整数,并且您想要找到INT_MAX(0x7fffffff)和INT_MIN(0x80000000)之间的差异.减法给出:

0x7fffffff - 0x80000000 = 0xffffffff
Run Code Online (Sandbox Code Playgroud)

这是-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
Run Code Online (Sandbox Code Playgroud)

  • 使用`jge` 可能会导致cpu 中的`branch predictor` 出现`mis-prediction`,这会显着降低cpu 的速度。因此,如果性能受到关注,最好使用来自 @bits 或 @Hal 的答案 (2认同)

小智 11

老线程,但如果我在这里冲浪很晚你可能也有... 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
Run Code Online (Sandbox Code Playgroud)

  • 通过避免“分支预测器”,这非常简单且高效,绝对应该被接受作为答案。 (2认同)

Tho*_*nin 5

假设您的整数在 MMX 或 XMM 寄存器中,用于psubd计算差异,然后pabsd获得差异的绝对值。

如果你的整数在普通的“正常”寄存器中,那么做一个减法,然后是cdq获得绝对值的技巧。这需要使用一些特定的寄存器(cdq符号扩展eaxedx,不使用其他寄存器),所以你可能想要用其他操作码做事。例如:

mov  r2, r1
sar  r2, 31
Run Code Online (Sandbox Code Playgroud)

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


Cal*_*lum 5

一种简短而直接的方法,使用条件移动指令(我认为可以使用奔腾及以上版本):

; compute ABS(r1-r2) in eax, overwrites r2
mov eax, r1
sub eax, r2
sub r2, r1
cmovg eax, r2
Run Code Online (Sandbox Code Playgroud)

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