算术右移给出虚假结果?
Posted
技术标签:
【中文标题】算术右移给出虚假结果?【英文标题】:Arithmetic right shift gives bogus result? 【发布时间】:2013-10-28 13:49:27 【问题描述】:我一定是疯了,但我机器上的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
我疯了吗?可能发生了什么?
【问题讨论】:
@ShafikYaghmour:这假设编译器甚至会费心输入指令。拒绝这个计划是完全有权利的。 @MSalters 确实,此时我们正在进入特定于编译器/平台/版本的内容,但对于当前和最近的版本,情况就是如此,正如我已经说过的那样,它是未定义的,所以你显然是靠自己的,gcc
似乎只在使用-O0
时产生shr
。
@ShafikYaghmour:英特尔只是 gcc 支持的众多平台之一,它们有不同的优化阶段。优化中的一个常见技巧是说“这个值只能在 0 和 31 之间,因为它用于移位,如果我按照代码路径 X 到达这里,值不会介于 0 和 31 之间,所以代码路径 X 是不可能的而且我什至不需要为它生成指令”。众所周知,GCC 会为空指针检查执行此操作。
@MSalters 有道理,所以这也会影响警告?
一个有趣的事实:虽然这是 C 中的 undefined 行为,但它是 C# 中的 defined 行为,但定义有些奇怪。在 C# 中,32 位整数 x 和 y 的 x>>y
计算为 x>>(y&0x1f)
!所以(x >> 16) >> 16
是零,但x >> 32
是x。
【参考方案1】:
在 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 指令的生成,我观察到了这一点。 编码之母...我刚刚测试了这个,100000的二进制是0b110000110101_00000
。我尝试移动 100001,它确实移动了 1。
@Aggieboy 它还将取决于其他因素,例如优化级别 gcc
和 clang
在不同的优化级别有不同的输出,这与未定义的行为完全一致,即任何事情都可能真正发生。【参考方案2】:
如果您的移位大于左操作数的位长度,则您正在调用undefined behavior,draft C++ standard 部分 5.8
移位运算符 段落 1 说( 强调我的):
操作数应为整数或非范围枚举类型,并执行整数提升。结果的类型是提升的左操作数的类型。 如果右操作数为负数,或者大于或等于提升的左操作数的位长度,则行为未定义。
有趣的是,gcc
和 clang
可能 如果移位量为 literal:
cout << (b>> 100000) ;
或者如果b
是一个const,gcc
的警告如下:
warning: right shift count >= width of type [enabled by default]
正如 MSalters 在问题的 cmets 中指出的那样,我们甚至可能无法依赖此警告,因为这是 未定义的行为,这与关于 未定义的标准说明一致术语和定义部分中的行为:
注意:[...] 允许的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),终止翻译或执行(发出诊断消息)。 [...]
平台具体细节
对于示例代码中明显缺乏移位的潜在解释可能是因为在某些平台上,移位计数将被屏蔽为5 bits
,例如在x86
架构上,我们可以请参阅 IA-32 架构兼容性 部分中的 Intel® 64 and IA-32 Architectures Software Developer’s Manual 部分 SAL/SAR/SHL/SHR-Shift 说:
8086 不屏蔽移位计数。但是,所有其他 IA-32 处理器(从 Intel 286 处理器开始)都会将移位计数屏蔽为 5 位,从而导致最大计数为 31。[...]
【讨论】:
80286 故意允许移位量达到字长(含)。我想知道为什么 80386 没有这样做(当操作数大小为 32 位时使用 6 位 CL)? 有关 x86 的更多背景信息,另请参阅 this blog post。以上是关于算术右移给出虚假结果?的主要内容,如果未能解决你的问题,请参考以下文章