在x86机器上移位超过32位的uint64_t整数未定义的行为?

学习困难的方法,我试图在x86机器上留下很long longuint64_t到超过32位的结果为0 。 我依稀记得曾经读过某个地方,而不是32位机器移位操作符只能在前32位工作但不能重新收集源。 我想知道是否在x86机器上移动超过32位的uint64_t整数是未定义的行为?

标准说(n1570中为6.5.7):

3对每个操作数执行整数提升。 结果的类型是提升的左操作数的类型。 如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。

4 E1 << E2的结果是E1左移E2位位置; 腾出的位用零填充。 如果E1具有无符号类型,则结果的值为E1×2 E2 ,比结果类型中可表示的最大值减少一个模数。 如果E1具有带符号类型和非负值,并且E1×2 E2可在结果类型中表示,那么这就是结果值; 否则,行为是未定的。

5 E1 >> E2的结果是E1右移E2位的位置。 如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1 / 2 E2的商的整数部分。 如果E1具有带符号类型和负值,则结果值是实现定义的。

标准完全定义了将uint64_t移动小于64位的距离。

由于long long必须至少为64位,因此如果结果不溢出,则非负值的标准定义小于64位的long long值。

但请注意,如果你编写一个符合32位的文字,例如@drhirsch推测的uint64_t s = 1 << 32 ,你实际上并没有移动64位值而是移位32位值。 那是未定义的行为。 最常见的结果是shift_distance % 32或0的移位,具体取决于硬件的作用。

Daniel Fischer的回答回答了关于C语言规范的问题。 至于在按可变数量发出class次时x86机器上实际发生的情况,请参阅英特尔软件开发人员手册第2B卷,p。 4-506:

计数被屏蔽为5位(如果在64位模式下使用REX.W则为6位)。 计数范围限制为0到31(如果使用64位模式和REX.W,则为63)。

因此,如果您尝试移动大于31或63的数量(分别对于32位和64位值),硬件将仅使用移位量的底部5或6位。 所以这段代码:

 uint32_t RightShift(uint32_t value, uint32_t count) { return value >> count; } 

将导致在x86和x86-64上的RightShift(2, 33) == 1 。 根据C标准,它仍然是未定义的行为 ,但是在x86上,如果编译器将其编译为sar指令,它将在该体系结构上定义行为。 但是你仍然应该避免编写这种依赖于特定于体系结构的怪癖的代码。

C标准要求转换正常工作。 特定的错误编译器可能有您描述的缺陷,但这是错误的行为。

这是一个测试程序:

 #include  #include  int main(void) { uint64_t x = 1; for (int i = 0; i < 64; i++) printf("%2d: 0x%.16" PRIX64 "\n", i, (x << i)); return 0; } 

这是运行RHEL 5和GCC 4.1.2的i686机器上的输出,也是x86 / 64机器(也运行RHEL 5和GCC 4.1.2)和x86 / 64 Mac(运行Mac OS X 10.7)的输出。 3与GCC 4.7.0)。 由于这是预期的结果,我得出结论,在32位机器上没有必要的问题,并且GCC至少没有表现出任何这样的错误,因为GCC 4.1.2(并且可能从未表现出这样的错误)。

  0: 0x0000000000000001 1: 0x0000000000000002 2: 0x0000000000000004 3: 0x0000000000000008 4: 0x0000000000000010 5: 0x0000000000000020 6: 0x0000000000000040 7: 0x0000000000000080 8: 0x0000000000000100 9: 0x0000000000000200 10: 0x0000000000000400 11: 0x0000000000000800 12: 0x0000000000001000 13: 0x0000000000002000 14: 0x0000000000004000 15: 0x0000000000008000 16: 0x0000000000010000 17: 0x0000000000020000 18: 0x0000000000040000 19: 0x0000000000080000 20: 0x0000000000100000 21: 0x0000000000200000 22: 0x0000000000400000 23: 0x0000000000800000 24: 0x0000000001000000 25: 0x0000000002000000 26: 0x0000000004000000 27: 0x0000000008000000 28: 0x0000000010000000 29: 0x0000000020000000 30: 0x0000000040000000 31: 0x0000000080000000 32: 0x0000000100000000 33: 0x0000000200000000 34: 0x0000000400000000 35: 0x0000000800000000 36: 0x0000001000000000 37: 0x0000002000000000 38: 0x0000004000000000 39: 0x0000008000000000 40: 0x0000010000000000 41: 0x0000020000000000 42: 0x0000040000000000 43: 0x0000080000000000 44: 0x0000100000000000 45: 0x0000200000000000 46: 0x0000400000000000 47: 0x0000800000000000 48: 0x0001000000000000 49: 0x0002000000000000 50: 0x0004000000000000 51: 0x0008000000000000 52: 0x0010000000000000 53: 0x0020000000000000 54: 0x0040000000000000 55: 0x0080000000000000 56: 0x0100000000000000 57: 0x0200000000000000 58: 0x0400000000000000 59: 0x0800000000000000 60: 0x1000000000000000 61: 0x2000000000000000 62: 0x4000000000000000 63: 0x8000000000000000 

不行,没关系。

ISO 9899:2011 6.5.7按位移位算子

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

情况并非如此,所以它很好并且定义明确。

通过包含在0和该类型宽度的前趋之间的数字的移位不会导致未定义的行为,但是左移一个负数会起作用。 你会这样做吗?

另一方面,对负数进行右移是实现定义的,并且大多数编译器在右移有符号类型时传播符号位。