10

如果移位运算符后的值大于左侧操作数的位数,则结果未定义。如果左侧操作数是无符号的,则右移是逻辑移位,因此高位将用零填充。如果左侧操作数有符号,则右移可能是也可能不是逻辑移位(即行为未定义)。

有人可以解释一下上述几行的含义吗?

4

6 回答 6

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、除以>>38 等相同。在正值的二进制表示中,除以 2 与将所有位向右移动一位,丢弃最小位,并用 0 填充最大位相同。

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

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

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

如果左边的操作数是有符号的,并且值为负,那么结果值是实现定义的。不允许坠毁或着火。实施必须产生结果,实施文档必须包含足够的信息来定义结果。在实践中,“实现文档”从编译器文档开始,但这可能会隐式或显式地将您引向操作系统和/或 CPU 的其他文档。

再次来自标准 5.8/3:

如果E1具有带符号类型和负值,则结果值是实现定义的。

于 2010-03-30T19:18:27.823 回答
5

我假设你知道转移意味着什么。假设您正在处理 8-bit chars

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

第一个转变,编译器可以随意做任何事情,因为 9 > 8 [a 中的位数char]。未定义的行为意味着所有的赌注都没有了,没有办法知道会发生什么。第二个转变定义明确。你在左边得到 0:11111111变成00001111. 第三个转变和第一个转变一样,是未定义的。

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

于 2010-03-30T19:00:47.917 回答
5

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

这意味着(unsigned int)x >> 33 可以做任何事情[1]

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

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

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

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

[1]:在具有 32 位int.

于 2010-03-30T19:00:50.973 回答
2

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

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

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

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

所以1100 >> 1 == 0110

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

如果数据类型是有符号的,则行为未定义。有符号数据类型以特殊格式存储,其中最左边的位表示正数或负数。因此,在有符号整数上移动可能无法达到您的预期。有关详细信息,请参阅维基百科文章。

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

于 2010-03-30T18:59:59.430 回答
2

为了给出一些上下文,这是该段的开头:

移位运算符也操作位。左移运算符 (<<) 生成运算符左侧的操作数,该运算符左移运算符后指定的位数。右移运算符 (>>) 生成运算符左侧的操作数,该运算符右移运算符后指定的位数。

现在剩下的,有解释:

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

如果您有一个 32 位整数并且您尝试对 33 位进行位移,这是不允许的,并且结果是未定义的。换句话说,结果可能是任何东西,或者您的程序可能会崩溃。

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

这表示它被定义为a >> b在 a 是无符号整数时写入。当您向右移动时,最低有效位被移除,其他位被向下移动,最高有效位变为零。

换句话说:

这个:110101000101010 >> 1
变为:011010100010101

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

实际上,我相信这里的行为是定义何时a为负,何时定义a为正,而不是引用中建议的未定义。这意味着如果你做的a >> b时候a是一个负整数,可能会发生很多不同的事情。要查看您得到的内容,您应该阅读编译器的文档。一个常见的实现是如果数字为正则移入零,如果数字为负则移入零,但如果您希望编写可移植代码,则不应依赖此行为。

于 2010-03-30T19:13:56.447 回答
1

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

第一句话说如果您尝试将 32 位值移位超过 32 位,则它是未定义的。

第二个说如果你将一个无符号整数右移,左边的位将被零填充。

第三个说如果您将有符号的 int 向右移动,则未定义将放在左侧位中的内容。

于 2010-03-30T18:57:20.803 回答