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

时间:2012-05-08 12:59:57

标签: c++ c x86 bit-shift uint64

艰难地学习,我试图在x86计算机上将long longuint64_t移位到32位以上0。我依稀记得在32位机器上移动操作符只能在前32位上操作,但不能重新收集源。 我想知道是否在x86机器上移动超过32位的uint64_t整数是未定义的行为?

5 个答案:

答案 0 :(得分:22)

标准说(n1570中为6.5.7):

  

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

     

4<<<<<< 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位,例如{@ 1}}正如@drhirsch所推测的那样,你实际上并没有移动64位值而是移位32位值。这是未定义的行为。

最常见的结果是uint64_t s = 1 << 32或0的转换,具体取决于硬件的作用(假设编译器的编译时评估模拟硬件语义,而不是鼻子恶魔。)

使用shift_distance % 32 在班次之前制作班次操作数1ULL < 63

答案 1 :(得分:4)

C标准要求班次正常运作。一个特定的错误编译器可能有您描述的缺陷,但这是错误的行为。

这是一个测试程序:

#include <stdio.h>
#include <inttypes.h>

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

答案 2 :(得分:4)

Daniel Fischer's answer回答了有关C语言规范的问题。至于在按可变数量发出班次时x86机器上实际发生的情况,请参阅Intel Software Developer Manual第2B卷第2页。 4-506:

  

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

因此,如果您尝试移动大于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指令,它将在该体系结构上定义行为。但是你仍然应该避免编写这种依赖于特定于体系结构的怪癖的代码。

答案 3 :(得分:1)

按0和前一个类型宽度之间的数字进行移位不会导致未定义的行为,但是左移一个负数就行了。你会这样做吗?

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

答案 4 :(得分:1)

不行。

ISO 9899:2011 6.5.7按位移位运算符

  

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

这不是这种情况,因此它很好并且定义明确。