为什么编译器除以2时会产生右移31位的结果? [英] Why does the compiler generate a right-shift by 31 bits when dividing by 2?

查看:169
本文介绍了为什么编译器除以2时会产生右移31位的结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have disassembled code produced by the compiler, and I see that it has produced the following sequence of instructions:

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

此代码的目的是什么?

我知道

sar     eax, 1

除以2,但会做什么

shr     eax, 1Fh

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

do? Does this mean that EAX will be either 0 or 1 if the left bit was either 0 or 1?

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

This looks strange to me! Can someone explain it?

推荐答案

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

The quick answer to your question—what is shr eax, 1Fh—is that it serves to isolate the uppermost bit of eax. It might be easier to understand if you convert the hexadecimal 1Fh to decimal 31. Now, you see that you're shifting eax right by 31. Since eax is a 32-bit value, shifting its bits right by 31 will isolate the very top bit, such that eax will contain either 0 or 1, depending on what the original value was of bit 31 (assuming that we start numbering bits with 0).

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

This is a common trick for isolating the sign bit. When a value is interpreted as a signed integer on a two's-complement machine, the uppermost bit is the sign bit. It is set (== 1) if the value is negative, or clear (== 0) otherwise. Of course, if the value is interpreted as an unsigned integer, the uppermost bit is just another bit used for storing its value, so the uppermost bit has an arbitrary value.

逐行进行反汇编,这是代码的作用:

Going line by line through the disassembly, here's what the code does:

mov     eax, edx

很明显,输入是在EDX中.该指令将值从EDX复制到EAX.这样,后续代码就可以操作EAX中的值而不会丢失原始值(在EDX中).

Evidently, the input was in EDX. This instruction copies the value from EDX into EAX. This allows subsequent code to manipulate the value in EAX without losing the original (in EDX).

shr     eax, 1Fh

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

Shift EAX right by 31 places, thus isolating the uppermost bit. Assuming that the input value is a signed integer, this will be the sign bit. EAX will now contain 1 if the original value was negative, or 0 otherwise.

add     eax, edx

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

Add the original value (EDX) to our temporary value in EAX. If the original value was negative, this will add 1 to it. Otherwise, it will add 0.

sar     eax, 1

EAX右移1位.此处的区别在于,这是算术的右移,而SHR逻辑的右移.逻辑移位用0填充新暴露的位.算术移位会将最高位(符号位)复制到新暴露的位.

Shift EAX right by 1 place. The difference here is that this is an arithmetic right shift, whereas SHR is a logical right shift. A logical shift fills the newly-exposed bits with 0s. An arithmetic shift copies the uppermost bit (the sign bit) to the newly-exposed bit.

将所有内容放在一起,这是一个标准习语,用于将有符号整数值除以2,以确保负值正确舍入.

Putting it all together, this is a standard idiom for dividing a signed integer value by 2 to ensure that negative values are correctly rounded.

unsigned 值除以2时,只需简单的移位即可.因此:

When you divide an unsigned value by 2, a simple bit-shift is all that is required. Thus:

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

等效于:

shr  eax, 1

但是,当除以有符号的值时,必须处理符号位.您可以使用sar eax, 1来实现有符号整数除以2,但这将导致结果值四舍五入为负无穷大.请注意,这与DIV/IDIV指令的行为不同,后者总是四舍五入.如果要模拟舍入为零的行为,则需要一些特殊的处理,这正是您所执行的代码所要做的.实际上,当您编译以下函数时,GCC,Clang,MSVC以及可能所有其他编译器都将精确地生成此代码:

But when dividing a signed value, you must deal with the sign bit. You could use sar eax, 1 to implement a signed integer division by 2, but this will cause the resulting value to be rounded toward negative infinity. Note that that is different than the behavior of the DIV/IDIV instruction, which always rounds towards zero. If you want to emulate the round-towards-zero behavior, you need some special handling, which is precisely what the code you have does. In fact, GCC, Clang, MSVC, and probably every other compiler will all generate precisely this code when you compile the following function:

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

这是非常的老把戏.迈克尔·阿布拉什(Michael Abrash)在1990年左右出版的《汇编语言的禅宗》中对此进行了讨论.(

This is a very old trick. Michael Abrash discussed it in his Zen of Assembly Language, published circa 1990. (Here is the relevant section in an online copy of his book.) It was surely common knowledge among assembly-language gurus long before that.

这篇关于为什么编译器除以2时会产生右移31位的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆