算术右移给出虚假的结果?

Sue*_*ode 30 c++ gcc bit-shift undefined-behavior

我必须在这里绝对疯狂,但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;
}
Run Code Online (Sandbox Code Playgroud)

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

100000
100000
100000
Run Code Online (Sandbox Code Playgroud)

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

dav*_*mac 46

在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++标准的最新草案; 我不确定它是否是最好用的,但同样,这个条款没有改变.

  • @GrijeshChauhan,这是在C和C++标准规范中记录的.关于SHR指令的生成,我观察到了这一点. (3认同)
  • @Aggieboy它还将依赖于优化级别`gcc`和`clang`之类的其他内容在不同的优化级别具有不同的输出,这与未定义的行为完全一致,即任何事情都可能真的发生. (2认同)

Sha*_*our 31

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

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

有趣的是,如果文字的移位金额,这两个gcc并且clang 可能会生成此代码的警告:

cout << (b>> 100000) ;
Run Code Online (Sandbox Code Playgroud)

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

warning: right shift count >= width of type [enabled by default]
Run Code Online (Sandbox Code Playgroud)

作为MSalters在评论的问题指出,我们可能无法甚至靠这个警告,因为这是不确定的行为,这是符合标准的注意未定义行为术语和定义部分,其表示:

注意:[...]允许的未定义行为范围从完全忽略不完全结果的情况到在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的文档化方式进行操作,以终止翻译或执行(发布诊断信息).[...]

平台特定细节

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

8086不掩盖班次计数.但是,所有其他IA-32处理器(从Intel 286处理器开始)确实将移位计数屏蔽为5位,最大计数为31. [...]