C ++中的Shift运算符

时间:2010-03-30 18:50:10

标签: c++ operators bit-shift

  

如果移位运算符后的值   大于的位数   左手操作数,结果是   未定义。如果左边的操作数是   无符号,右移是合乎逻辑的   移位所以高位将被填充   用零。如果是左手操作数   已经签署,正确的转变可能或可能   不是一个逻辑转变(即,   行为未定义)。

有人能解释一下上面这些行是什么意思吗?

6 个答案:

答案 0 :(得分:23)

这些线的含义并不重要,它们实际上是不正确的。

“如果移位运算符后的值 大于的位数 左手操作数,结果是 未定义“。

是真的,但应该说“大于或等于”。 5.8 / 1:

  

...如果是,行为是不确定的   右手操作数是否定的,或者   大于或等于的长度   升级左操作数的位。

未定义的行为意味着“不要这样做”(见后文)。也就是说,如果您的系统上int为32位,那么您无法有效地执行以下任何操作:

int a = 0; // this is OK
a >> 32;   // undefined behavior
a >> -1;   // UB
a << 32;   // UB
a = (0 << 32); // Either UB, or possibly an ill-formed program. I'm not sure.

“如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。”

这是事实。 5.8 / 3说:

  

如果E1具有无符号类型或E1具有   签名类型和非负值,   结果是整体的一部分   E1的商除以数量   2提升到权力E2

如果这对你更有意义。 >>1与除以2相同,>>2除以4,>>3除以8,依此类推。在正值的二进制表示中,除以2与向右移动所有位,丢弃最小位,并用0填充最大位相同。

“如果左侧操作数已签名,则右移可能是也可能不是逻辑移位(即行为未定义)。”

第一部分是正确的(它可能是也可能不是逻辑转变 - 它在某些编译器/平台上但不在其他编译器/平台上。我认为到目前为止最常见的行为是它不是)。第二部分是假的,行为不是未定义的。未定义的行为意味着允许任何事情发生 - 崩溃,恶魔飞出你的鼻子,随机值,等等。标准不关心。有很多情况下C ++标准表明行为是未定义的,但这不是其中之一。

实际上,如果左手操作数是有符号的,并且值是正的,那么它的行为与无符号的移位相同。

如果左手操作数已签名,且值为负,则结果值是实现定义的。它不允许坠毁或着火。实现必须产生结果,并且实现的文档必须包含足够的信息来定义结果。实际上,“实现文档”从编译器文档开始,但这可能会隐式或显式地引用您的操作系统和/或CPU的其他文档。

再次从标准,5.8 / 3:

  

如果E1已签署类型且为负数   值,结果值是   实现定义的。

答案 1 :(得分:5)

我假设你知道改变意味着什么。假设您正在处理8位char s

unsigned char c;
c >> 9;
c >> 4;
signed char c;
c >> 4;

第一个转变,编译器可以自由地做任何想做的事,因为9&gt; 8 [char]中的位数。未定义的行为意味着所有赌注都已关闭,无法知道将会发生什么。第二个转变很明确。您在左侧获得0:11111111变为00001111。第三个转变与第一个转变一样,未定义。

请注意,在第三种情况下,c的值无关紧要。当它引用signed时,它表示变量的类型,而不是实际值是否大于零。 signed char c = 5signed char c = -5都已签名,向右移动是未定义的行为。

答案 2 :(得分:5)

  

如果移位运算符之后的值大于左侧操作数中的位数,则结果是未定义的。

这意味着(unsigned int)x >> 33 can do anything [1]

  

如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。

这意味着0xFFFFFFFFu >> 4必须是0x0FFFFFFFu

  

如果左侧操作数已签名,则右移可能是也可能不是逻辑移位(即行为未定义)。

这意味着0xFFFFFFFF >> 4可以是0xFFFFFFFF(算术移位)或0x0FFFFFFF(逻辑移位)或任何物理法允许的,即结果未定义。

[1]:在具有32位int的32位机器上。

答案 3 :(得分:2)

  

如果移位运算符后的值   大于的位数   左手操作数,结果是   未定义。

如果您尝试将32位整数移位33,则结果未定义。即,它可能全部为零,也可能不是全零。

  

如果左侧操作数未签名,   正确的转变是合乎逻辑的转变   高位将填充   零。

右移时,无符号数据类型将用零填充。

所以1100 >> 1 == 0110

  

如果左手操作数已签名,   正确的转变可能是也可能不是   逻辑转变(即行为   未定义)。

如果数据类型已签名,则不会定义行为。签名数据类型以特殊格式存储,其中最左边的位表示正或负。因此,对有符号整数进行移位可能无法达到预期效果。有关详细信息,请参阅Wikipedia文章。

http://en.wikipedia.org/wiki/Logical_shift

答案 4 :(得分:2)

为了给出一些背景,这是该段的开头:

  

移位运算符也操纵位。左移位运算符(&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;右移运算符(&gt;&gt;)产生运算符左侧的操作数,向右移动运算符后指定的位数。

现在剩下的就是解释:

  

如果移位运算符之后的值大于左侧操作数中的位数,则结果是未定义的。

如果你有一个32位整数,你试图将33位移位,那是不允许的,结果是未定义的。换句话说,结果可能是任何结果,或者您的程序可能崩溃。

  

如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。

这表示当a是unsigned int时,它被定义为写a >> b。向右移位时,最低有效位被移除,其他位向下移位,最高有效位变为零。

换句话说:

This:    110101000101010 >> 1
becomes: 011010100010101
  

如果左侧操作数已签名,则右移可能是也可能不是逻辑移位(即行为未定义)。

实际上我认为这里的行为是在a为负时定义的实现,并且当a为正时定义,而不是如引用中所建议的那样未定义。这意味着如果在a >> b为负整数时执行a,则可能会发生许多不同的事情。要查看您获得的内容,您应该阅读编译器的文档。一个常见的实现是如果数字为正数则​​为零,如果数字为负数则为1,但如果您希望编写可移植代码,则不应依赖此行为。

答案 5 :(得分:1)

我认为关键词是“未定义”,这意味着规范没有说明应该发生什么。大多数编译器会在这种情况下做一些合理的事情,但你不能完全依赖任何行为。通常最好避免调用未定义的行为,除非您使用的编译器的文档说明了它在特定情况下的作用。

如果您尝试将32位值移位超过32位,则第一句话表示未定义。

第二个说如果右移一个unsigned int,左边的位将被零填充。

第三个说如果你正确地移动一个有符号的int,那么就没有定义将放在左手位中的内容。