C 中的位移位无符号长整型

Bit-shifting unsigned longs in C

我在我写的一段代码中发现了一个错误,并已修复它,但仍然无法解释发生了什么。归结为:

unsigned i = 1<<31;          // gives 21476483648 as expected
unsigned long l = 1<<31;     // gives 18446744071562067968 as not expected

我知道这里有一个问题:Unsigned long and bit shifting 其中完全相同的数字显示为意外值,但他使用的是带符号的字符,我认为这导致了符号扩展。我真的不能为我的生活明白为什么我在这里得到一个不正确的值。

我在 Ubuntu 18.04 上使用 CLion,在我的系统上 unsigned 是 32 位,long 是 64 位。

在这个表达式中:

1<<31

1 的类型为 int。假设 int 是 32 位宽,这意味着您要将一位移入符号位。这样做是undefined behavior.

这在 C standard 的第 6.5.7p4 节中有记录:

The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1×2E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1×2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

但是,由于您在使用 GCC 的 Ubuntu 上,行为实际上是实现定义的。 gcc documentation 状态:

Bitwise operators act on the representation of the value including both the sign and value bits, where the sign bit is considered immediately above the highest-value value bit. Signed >> acts on negative numbers by sign extension.

As an extension to the C language, GCC does not use the latitude given in C99 and C11 only to treat certain aspects of signed << as undefined. However, -fsanitize=shift (and -fsanitize=undefined) will diagnose such cases. They are also diagnosed where constant expressions are required.

所以 gcc 在这种情况下直接作用于值的表示。这意味着 1<<31 具有类型 int 和表示 0x80000000。此表示法的十进制值为 -2147483648.

当这个值赋给一个unsigned int时,它通过6.3.1.3p2节中的规则转换:

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

因为对于 32 位 "one more than the maximum value" 是 42949672956 unsigned int 这导致 int 值 -2147483648 被转换为 unsigned int 值 42949672956 -2147483648 == 2147483648.

1<<31赋值为64位的unsigned long int时,"one more than the maximum value"为18446744073709551616所以转换后的结果为18446744073709551616 -2147483648 == 18446744071562067968,也就是这个值你得到了。

要获得正确的值,请使用 UL 后缀使值 unsigned long:

1UL<<31