为什么编译器在除以2时会产生31位的右移?

时间:2016-11-16 17:18:25

标签: assembly optimization x86 disassembly sar

我已经反汇编了编译器生成的代码,我发现它产生了以下指令序列:

mov     eax, edx
shr     eax, 1Fh
add     eax, edx
sar     eax, 1  

此代码的目的是什么?

我知道

sar     eax, 1

除以2,但

是什么
shr     eax, 1Fh

做什么?这是否意味着如果左位为0或1,则EAX将为0或1?

这对我来说很奇怪!有人可以解释一下吗?

1 个答案:

答案 0 :(得分:3)

您问题的快速答案 - shr eax, 1Fh是什么 - 它可以隔离eax的最高位。如果将十六进制1Fh转换为十进制31,可能会更容易理解。现在,您看到您正在将eax向右移动31.由于eax是32位值,将其位右移31将使最顶部位隔离,这样eax 1}}将包含0或1,具体取决于第31位的原始值(假设我们开始用0编号位)。

这是隔离符号位的常用技巧。当在2的补码机器上将值解释为有符号整数时,最高位是符号位。如果值为负,则设置(== 1),否则设置为clear(== 0)。当然,如果该值被解释为无符号整数,则最高位只是用于存储其值的另一位,因此最高位具有任意值。

通过反汇编逐行,这里是代码的作用:

mov     eax, edx

显然,输入位于EDX。该指令将值从EDX复制到EAX。这允许后续代码操纵EAX中的值而不会丢失原始值(在EDX中)。

shr     eax, 1Fh

EAX右移31个位置,从而隔离最高位。假设输入值是有符号整数,这将是符号位。如果原始值为负,EAX现在将包含1,否则为0。

add     eax, edx

将原始值(EDX)添加到EAX中的临时值。如果原始值为负,则会将其加1。否则,它将添加0。

sar     eax, 1

EAX右移1个地方。这里的区别在于这是算术右移,而SHR逻辑右移。逻辑移位用0填充新曝光的位。算术移位将最高位(符号位)复制到新曝光的位。

总而言之,这是将有符号整数值除以2的标准习惯用法,以确保负值正确舍入

当您将无符号值除以2时,只需要一个简单的位移。因此:

unsigned Foo(unsigned value)
{
    return (value / 2);
}

相当于:

shr  eax, 1

但是在划分有符号值时,必须处理符号位。您可以使用sar eax, 1来实现有符号整数除以2,但这会导致结果值向负无穷大舍入。请注意,这与DIV / IDIV指令的行为不同,后者始终向零舍入。如果你想模仿圆向零的行为,你需要一些特殊的处理,这正是你所拥有的代码所做的。实际上,在编译以下函数时,GCC,Clang,MSVC以及可能所有其他编译器都将生成这些代码:

int Foo(int value)
{
    return (value / 2);
}

这是非常旧技巧。迈克尔·阿布拉什(Michael Abrash)在他的汇编语言之禅中讨论过它,发表于大约 1990.(Here is the relevant section在他的书的在线副本中。)这当然是常识汇编语言专家早就开始了。