【问题标题】:Why does the compiler generate a right-shift by 31 bits when dividing by 2?为什么编译器在除以 2 时会产生 31 位的右移?
【发布时间】:2016-11-16 17:18:25
【问题描述】:

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

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

这段代码的目的是什么?


我知道

sar     eax, 1

除以 2 是什么

shr     eax, 1Fh

做吗?这是否意味着如果左边的位是 0 或 1,EAX 将是 0 或 1?

这对我来说很奇怪!谁能解释一下?

【问题讨论】:

    标签: assembly optimization x86 disassembly sar


    【解决方案1】:

    对您的问题(shr eax, 1Fh 是什么)的快速回答是,它用于隔离 eax 的最高位。如果将十六进制的1Fh 转换为十进制的31,可能会更容易理解。现在,您看到您正在将eax 右移 31。由于eax 是一个 32 位值,因此将其位右移 31 将隔离最高位,这样eax 将包含 0 或1,取决于第 31 位的原始值(假设我们从 0 开始对位进行编号)。

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


    逐行反汇编,代码的作用如下:

    mov     eax, edx
    

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

    shr     eax, 1Fh
    

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

    add     eax, edx
    

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

    sar     eax, 1
    

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


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

    当您将 unsigned 值除以 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 在他的Zen of Assembly Language 中讨论了它,该书大约 1990 年出版。(Here is the relevant section 在他的书的在线副本中。)这肯定是汇编中的常识 -早在那之前的语言大师。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多