快速回答您的问题——什么是shr eax, 1Fh
- 是它用于隔离最上面的位eax
。如果把十六进制转换一下可能会更容易理解1Fh
到小数31
。现在,你发现你正在转变eax
31 号之后。eax
是一个 32 位值,将其位右移 31 将隔离最高位,这样eax
将包含 0 或 1,具体取决于位 31 的原始值(假设我们从 0 开始对位进行编号)。
这是隔离的常见技巧sign bit。当一个值在补码机上被解释为有符号整数时,最高位是符号位。如果值为负则设置 (== 1),否则清除 (== 0)。当然,如果该值被解释为无符号整数,则最高位只是用于存储其值的另一位,因此最高位具有任意值。
逐行进行反汇编,代码的作用如下:
mov eax, edx
显然,输入是在EDX
。该指令复制值EDX
into EAX
。这允许后续代码操作中的值EAX
不丢失原来的(在EDX
).
shr eax, 1Fh
Shift EAX
右移 31 位,从而隔离最高位。假设输入值是有符号整数,这将是符号位。EAX
如果原始值为负,则现在将包含 1,否则将包含 0。
add eax, edx
添加原始值(EDX
)到我们的临时值EAX
。如果原始值为负数,则会加 1。否则就加0。
sar eax, 1
Shift EAX
就差 1 个位置。这里的区别在于这是一个算术右移,而SHR
is a logical右移。逻辑移位用 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);
}
这是一个very老把戏。迈克尔·阿布拉什 (Michael Abrash) 在他的汇编语言之禅,发表circa 1990. (这是相关部分 http://www.jagregory.com/abrash-zen-of-asm/#signed-division-with-sar在那之前,这肯定是汇编语言专家们的常识。