我已经反汇编了编译器生成的代码,我看到它生成了以下指令序列:
mov eax, edx
shr eax, 1Fh
add eax, edx
sar eax, 1
这段代码的目的是什么?
我知道
sar eax, 1
除以 2,但是什么
shr eax, 1Fh
做?这是否意味着EAX
如果左边的位是 0 或 1,它将是 0 或 1?
这对我来说看起来很奇怪!有人可以解释一下吗?
我已经反汇编了编译器生成的代码,我看到它生成了以下指令序列:
mov eax, edx
shr eax, 1Fh
add eax, edx
sar eax, 1
这段代码的目的是什么?
我知道
sar eax, 1
除以 2,但是什么
shr eax, 1Fh
做?这是否意味着EAX
如果左边的位是 0 或 1,它将是 0 或 1?
这对我来说看起来很奇怪!有人可以解释一下吗?
对您的问题(是什么)的快速回答是shr eax, 1Fh
,它用于隔离eax
. 如果将 hexadecimal 转换1Fh
为 decimal可能更容易理解31
。现在,您看到您正在右移eax
31。由于eax
是 32 位值,因此将其位右移 31 将隔离最高位,这样eax
将包含 0 或 1,具体取决于原始值是什么位 31(假设我们从 0 开始编号位)。
这是隔离符号位的常用技巧。当一个值在二进制补码机器上被解释为有符号整数时,最高位是符号位。如果值为负,则设置 (== 1),否则清除 (== 0)。当然,如果将该值解释为无符号整数,则最高位只是用于存储其值的另一位,因此最高位具有任意值。
逐行通过反汇编,这是代码的作用:
mov eax, edx
显然,输入在EDX
. 该指令将值从 复制EDX
到EAX
。这允许后续代码在EAX
不丢失原始 (in EDX
) 的情况下操作 in 的值。
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 年出版的《汇编语言之禅》中讨论了它。(这里是他的书的在线副本中的相关部分。)在此之前,汇编语言大师们肯定是常识。