在C语言中对无符号长线进行位移操作

问题描述 投票:3回答:1

我在我写的一段代码中发现了一个bug,并且已经修复了它,但仍然无法解释发生了什么。归根结底是这样的。

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

我发现了一个问题 无符号长和位移 其中完全相同的数字显示为一个意外的值,但他使用了一个有符号的字符,我相信这导致了一个符号扩展。我真的不明白为什么我在这里会得到一个错误的值。

我在Ubuntu 18.04上使用的是CLion,在我的系统中,有一个叫 "L "的文件。unsigned 是32位和一个 long 是64位的。

c long-integer bit-shift
1个回答
7
投票

在这个表达式中。

1<<31

在这个表达式中: 1 有型 int. 假设 int 是32位宽,这意味着你要向符号位移位。 这样做是 未定义行为.

这一点在《公约》第6.5.7p4节中有记载。C标准:

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

然而,由于您使用的是Ubuntu,而Ubuntu使用的是GCC,因此该行为实际上是由实现定义的。 这个 gcc文档 态。

位元运算符作用于包括符号位和值位在内的值的表示,其中符号位被认为是紧靠在最高值位之上。符号位 >> 通过符号扩展作用于负数。

作为C语言的延伸,GCC没有使用C99和C11中给出的纬度,只是在处理某些方面的有符号的 << 为未定义。然而, -fsanitize=shift (和 -fsanitize=undefined)将诊断出这种情况。在需要常量表达式的情况下,也会对它们进行诊断。

所以在这种情况下,gcc直接工作在值的表示上。 这意味着 1<<31 有型 int 和代表性 0x80000000. 这个十进制表示的值是-2147483648。

当这个值被分配给一个 unsigned int,它通过6.3.1.3p2节的规则进行转换。

否则,如果新类型是无符号的,则通过反复加减新类型所能代表的最大值来转换数值,直到数值在新类型的范围内。

由于 "比最大值多一个 "对于32位的数据来说是42949672956 unsigned int 这就导致了 int 值-2147483648被转换为-2147483648。unsigned int 值42949672956 -2147483648 == 2147483648。

1<<31 被分配到一个 unsigned long int 是64位的,"比最大值多一个 "是18446744073709551616,所以换算的结果是18446744073709551616 -2147483648 == 18446744071562067968,这就是你得到的值。

为了得到正确的值,使用 UL 后缀,以使该值 unsigned long:

1UL<<31
© www.soinside.com 2019 - 2024. All rights reserved.