算术右移给出虚假结果?

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;

现在,任何自行右移的数字都应该得到 0n/(2^n) == 0 带有 整数除法n&gt;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&gt;&gt;y 计算为 x&gt;&gt;(y&amp;0x1f) !所以(x &gt;&gt; 16) &gt;&gt; 16 是零,但x &gt;&gt; 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 它还将取决于其他因素,例如优化级别 gccclang 在不同的优化级别有不同的输出,这与未定义的行为完全一致,即任何事情都可能真正发生。【参考方案2】:

如果您的移位大于左操作数的位长度,则您正在调用undefined behavior,draft C++ standard 部分 5.8 移位运算符 段落 1 说( 强调我的):

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

有趣的是,gccclang 可能 如果移位量为 literal

cout << (b>> 100000) ;

或者如果b 是一个constgcc 的警告如下:

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。

以上是关于算术右移给出虚假结果?的主要内容,如果未能解决你的问题,请参考以下文章

算术左移逻辑左移算术右移逻辑右移

操作符详解

信息的表示和处理

如何以可移植的方式在 C 中执行算术右移?

booth补码乘法算法中乘数的右移规则是啥?

汇编--逻辑指令