简体   繁体   English

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

[英]Is Shifting more than 32 bits of a uint64_t integer on an x86 machine Undefined Behavior?

Learning the hard way, I tried to left shift a long long and uint64_t to more than 32 bits on an x86 machine resulted 0 . 学习困难的方法,我试图在x86机器上留下很long longuint64_t到超过32位的结果为0 I vaguely remember to have read somewhere than on a 32 bit machine shift operators only work on the first 32 bits but cannot recollect the source. 我依稀记得在32位机器上移动操作符只能在前32位上操作,但不能重新收集源。 I would like to know is if Shifting more than 32 bits of a uint64_t integer on an x86 machine is an Undefined Behavior? 我想知道是否在x86机器上移动超过32位的uint64_t整数是未定义的行为?

The standard says (6.5.7 in n1570): 标准说(n1570中为6.5.7):

3 The integer promotions are performed on each of the operands. 3对每个操作数执行整数提升。 The type of the result is that of the promoted left operand. 结果的类型是提升的左操作数的类型。 If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined. 如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。

4 The result of E1 << E2 is E1 left-shifted E2 bit positions; 4 E1 << E2的结果是E1左移E2位位置; vacated bits are filled with zeros. 腾出的位用零填充。 If E1 has an unsigned type, the value of the result is E1 × 2 E2 , reduced modulo one more than the maximum value representable in the result type. 如果E1具有无符号类型,则结果的值为E1×2 E2 ,比结果类型中可表示的最大值减少一个模数。 If E1 has a signed type and nonnegative value, and E1 × 2 E2 is representable in the result type, then that is the resulting value; 如果E1具有带符号类型和非负值,并且E1×2 E2可在结果类型中表示,那么这就是结果值; otherwise, the behavior is undefined. 否则,行为未定。

5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. 5 E1 >> E2的结果是E1右移E2位的位置。 If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2 E2 . 如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1 / 2 E2的商的整数部分。 If E1 has a signed type and a negative value, the resulting value is implementation-defined. 如果E1具有有符号类型和负值,则生成的值是实现定义的。

Shifting a uint64_t a distance of less than 64 bits is completely defined by the standard. 标准完全定义了将uint64_t移动小于64位的距离。

Since long long must be at least 64 bits, shifting long long values less than 64 bits is defined by the standard for nonnegative values, if the result doesn't overflow. 由于long long必须至少为64位,如果结果不溢出,则非负值的标准定义小于64位的long long值。

Note, however, that if you write a literal that fits into 32 bits, eg uint64_t s = 1 << 32 as surmised by @drhirsch, you don't actually shift a 64-bit value but a 32-bit one. 但请注意,如果您编写一个符合32位的文字,例如@drhirsch推测的uint64_t s = 1 << 32 ,则实际上不会移动64位值而是移位32位值。 That is undefined behaviour. 这是未定义的行为。

The most common results are a shift by shift_distance % 32 or 0, depending on what the hardware does (and assuming the compiler's compile-time evaluation emulates the hardware semantics, instead of nasal demons.) 最常见的结果是shift_distance % 32或0的移位,具体取决于硬件的作用(并假设编译器的编译时评估模拟硬件语义,而不是鼻子恶魔。)

Use 1ULL < 63 to make the shift operand unsigned long long before the shift. 使用1ULL < 63使移位操作数在移位 unsigned long long

The C standard requires the shift to work correctly. C标准要求转换正常工作。 A particular buggy compiler might have the defect you describe, but that is buggy behaviour. 特定的错误编译器可能有您描述的缺陷,但这是错误的行为。

This is a test program: 这是一个测试程序:

#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;
}

This is the output on an i686 machine running RHEL 5 with GCC 4.1.2, and also on x86/64 machine (also running RHEL 5 and GCC 4.1.2), and on a x86/64 Mac (running Mac OS X 10.7.3 with GCC 4.7.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)。 Since that's the expected result, I conclude that there is no necessary problem on the 32-bit machine, and that GCC at least has not exhibited any such bug since GCC 4.1.2 (and probably never has exhibited such a bug). 由于这是预期的结果,我得出结论,在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

Daniel Fischer's answer answers the question about the C language specification. Daniel Fischer的回答回答了有关C语言规范的问题。 As for what actually happens on an x86 machine when you issue a shift by a variable amount, refer to the Intel Software Developer Manual Volume 2B, p. 至于在按可变数量发出班次时x86机器上实际发生的情况,请参阅英特尔软件开发人员手册第2B卷,p。 4-506: 4-506:

The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). 计数被屏蔽为5位(如果在64位模式下使用REX.W则为6位)。 The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used). 计数范围限制为0到31(如果使用64位模式和REX.W,则为63)。

So if you attempt to shift by an amount larger than 31 or 63 (for 32- and 64-bit values respectively), the hardware will only use the bottom 5 or 6 bits of the shift amount. 因此,如果您尝试移动大于31或63的值(分别对于32位和64位值),硬件将仅使用移位量的底部5或6位。 So this code: 所以这段代码:

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

Will result in RightShift(2, 33) == 1 on x86 and x86-64. 将导致在x86和x86-64上的RightShift(2, 33) == 1 It's still undefined behavior according to the C standard, but on x86, if the compiler compiles it down to a sar instruction, it will have defined behavior on that architecture. 根据C标准,它仍然是未定义的行为 ,但是在x86上,如果编译器将其编译为sar指令,它将在该体系结构上定义行为。 But you should still avoid writing this sort of code that depends on architecture-specific quirks. 但是你仍然应该避免编写依赖于特定于体系结构的怪癖的这种代码。

Shifting by a number comprised between 0 and the predecessor of the width of the type does not cause undefined behavior, but left-shifting a negative number does. 通过包含在0和该类型宽度的前任之间的数字的移位不会导致未定义的行为,但是左移一个负数会起作用。 Would you be doing that? 你会这样做吗?

On the other hand, right-shifting a negative number is implementation-defined, and most compilers, when right-shifting signed types, propagate the sign bit. 另一方面,对负数进行右移是实现定义的,并且大多数编译器在右移有符号类型时传播符号位。

No it is ok. 不行,没关系。

ISO 9899:2011 6.5.7 Bitwise shift operators ISO 9899:2011 6.5.7按位移位算子

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined . 如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义

That isn't the case here, so it is all fine and well-defined. 情况并非如此,因此它很好并且定义明确。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM