我必须在这里绝对疯狂,但我机器上的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;
}
现在,任何正确移位的数字都会导致 0 (n/(2^n) == 0
整数除法,n>1
和积极/无符号),但不知何故,这是我的输出:
100000
100000
100000
我疯了吗?可能会发生什么?
答案 0 :(得分:47)
在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 ++标准的最新草案;我不确定它是否是最好用的,但同样,这个条款没有改变。
答案 1 :(得分:32)
如果移位大于左操作数的位长,undefined behavior部分5.8
移位运算符段 1,则调用draft C++ standard 说(强调我的):
操作数应为整数或无范围的枚举类型,并执行整体促销。结果的类型是提升的左操作数的类型。 如果右操作数为负数,或者大于或等于提升左操作数的位长度,则行为未定义。
有趣的是,gcc
和clang
可能会为此代码生成警告,如果文字的移位金额:
cout << (b>> 100000) ;
或如果b
是 const ,gcc
的警告如下:
warning: right shift count >= width of type [enabled by default]
正如MSalters在问题的评论中指出的那样,我们可能无法依赖此警告,因为这是未定义的行为,这与 undefined上的标准说明一致术语和定义部分中的行为,其中包含:
注意:[...]允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征的文件化方式行事(有或没有发出诊断消息),终止翻译或执行(发布诊断消息)。 [...]
平台特定详情
示例代码明显缺乏转换的可能解释可能是因为在某些平台上,移位计数将屏蔽到5 bits
,例如在{{1}上}架构我们可以在 IA-32架构兼容性部分中看到Intel® 64 and IA-32 Architectures Software Developer’s Manual部分 SAL / SAR / SHL / SHR-Shift :
8086不会掩盖班次计数。但是,所有其他IA-32处理器(从Intel 286处理器开始)确实将移位计数屏蔽为5位,最大计数为31. [...]