32

我在这里一定是疯了,但gcc 4.7.3在我的机器上给出了最荒谬的结果。这是我正在测试的确切代码:

#include <iostream>

using namespace std;

int main(){
  unsigned int b = 100000;
  cout << (b>>b) << endl;
  b = b >> b;
  cout << b << endl;
  b >>= b;
  cout << b << endl;
  return 0;
}

现在,任何本身右移的数字都应该导致0n/(2^n) == 0带有整数除法n>1正/无符号),但不知何故这是我的输出:

100000
100000
100000

我疯了吗?可能会发生什么?

4

2 回答 2

49

在 C++ 和 C 中一样,移位仅限于移位值的大小(以位为单位)。例如,如果 unsigned int 是 32 位,则大于 31 的移位是未定义的。

在实践中,一个常见的结果是使用移位量的最低5位,忽略高位;这是因为编译器产生了一个机器指令,它正是这样做的(例如 x86 上的 SHR)。

在这种情况下,移位值是100000(十进制),恰好是11000011010100000二进制 - 低 5 位为零。因此,您实际上得到了 0 的转变。但是,您不应该依赖它;从技术上讲,您看到的是未定义的行为

参考:

对于 C,N1570第 6.5.7 节:

如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。

对于 C++,N3690第 5.8 节“[expr.shift]”:

如果右操作数为负数,或者大于或等于提升的左操作数的位长度,则行为未定义。

N1570 是一个草案,与已发布的 ISO C11 标准几乎相同;自 1989 年 ANSI C 标准以来,该条款几乎相同。

N3690 是 C++ 标准的最新草案;我不确定它是否是最好的,但同样,这个条款没有改变。

于 2013-10-28T13:51:40.733 回答
32

如果您的移位大于左操作数的位长度,您将调用未定义的行为,草案 C++ 标准部分5.8 移位运算符1段说(强调我的):

操作数应为整数或非范围枚举类型,并执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数为负数,或者大于或等于提升的左操作数的位长度,则行为未定义。

有趣的是,如果移位量是文字gcc,则两者都clang 可能会为此代码生成警告:

cout << (b>> 100000) ;

或者如果bconst,则警告gcc如下:

warning: right shift count >= width of type [enabled by default]

正如 MSalters 在对问题的评论中指出的那样,我们甚至可能无法依赖此警告,因为这是未定义的行为,这与术语和定义部分中关于未定义行为的标准说明一致,该说明说:

注意:[...] 允许的未定义行为的范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止一个翻译或执行(发布诊断消息)。[...]

平台特定细节

示例代码中明显缺乏移位的潜在解释可能是因为在某些平台上移位计数将被屏蔽5 bits例如在x86架构上,我们可以看到英特尔® 64 和 IA-32 架构软件开发人员手册部分SAL/ SAR/SHL/SHR — IA-32 架构兼容性部分中的转变说:

8086 不屏蔽移位计数。但是,所有其他 IA-32 处理器(从 Intel 286 处理器开始)都会将移位计数屏蔽为 5 位,从而导致最大计数为 31。 [...]

于 2013-10-28T13:51:52.137 回答