C/C++中左移操作符对无符号和有符号数的影响有何不同?

13

我有这段代码。

#include <iostream>

int main()
{
    unsigned long int i = 1U << 31;
    std::cout << i << std::endl;
    unsigned long int uwantsum = 1 << 31;
    std::cout << uwantsum << std::endl;
    return 0;
}

它打印出来。

2147483648
18446744071562067968

在 Arch Linux 64 位系统下,使用 gcc 编译,采用 Ivy Bridge 架构。

第一个结果有意义,但我不理解第二个数字来自何处。将 1 表示为 4 字节有符号或无符号整数时:

00000000000000000000000000000001

当您将其向左移31次时,最终得到的是

10000000000000000000000000000000

不会吧?我知道对于正数来说向左移位本质上等于2的k次方,其中k是你移位的次数,假设它仍然适合边界。为什么我得到了一个奇怪的数字?


bizarre number? 检查二进制形式。 - Bryan Chen
我在谈论第二行。已更新问题。 - No_name
5个回答

20

您可能对为什么这个表达式:unsigned long int uwantsum = 1 << 31; 会产生“奇怪”的值感兴趣。

问题很简单:1是一个普通的int类型,所以移位操作是在一个普通的int类型上进行的,且只有在操作完成后才转换为unsigned long类型。

然而,在这种情况下,1<<31超出了32位有符号整型的范围,因此结果是未定义的1。在转换为无符号类型之后,结果仍然是未定义的。

话虽如此,在大多数典型情况下,1<<31将给出一个位模式为10000000000000000000000000000000的值。当视为带符号的二进制补码2时,它表示-2147483648。由于这是负数,因此在将其转换为64位类型时,它将被符号扩展,因此顶部的32位将填充与第31位相同的内容。得到的结果是:1111111111111111111111111111111110000000000000000000000000000000(33个1位后面跟着31个0位)。

如果我们把它当作一个无符号的64位数字来处理,我们得到的是18446744071562067968。


  1. §5.8/2:
     

    E1 << E2的值是E1向左移动E2位;空出的位被填充为0。如果E1具有无符号类型,则结果的值为E1×2E2,对大于结果类型中可表示的最大值的数取模后的余数。否则,如果E1具有有符号类型且非负值,并且E1×2E2可以在结果类型对应的无符号类型中表示,则将该值转换为结果类型后得到的值即为结果;否则,行为未定义

  2. 从理论上讲,计算机可以使用反码或原码来表示有符号数 - 但目前比这两者更常用的是补码。如果使用其中任何一种,我们预期会得到不同的最终结果。

这很有道理。我不确定我是否遇到了未定义的行为。谢谢!我假设在像MSVC这样的不同编译器下,我会得到完全不同的结果? - No_name
@No_name:不能保证,但很有可能硬件会产生相同的结果,所以在同一CPU上使用两个编译器通常会产生相同的结果。 - Jerry Coffin
你说“都是普通的整数” - 只有 1 的类型才是重要的;1 << 31ull1 << 31 是一样的。 - M.M
@MattMcNabb:说得好--虽然从技术上讲是正确的,但这是误导性的。 - Jerry Coffin
“reduced modulo one more than the maximum value representable in the result type”是什么意思? - No_name
显示剩余6条评论

7
字面上没有U的数字1是一个有符号的int类型,因此当你进行左移操作时(<<31),会发生整数溢出,生成一个负数(在未定义行为的范畴内)。
将这个负数赋值给一个unsigned long类型会导致符号扩展,因为long类型比int类型有更多的位,它通过取模2的64次方来将负数转换成大的正数,这是有符号转无符号的规则。

3
这并不是什么“奇怪”的东西。尝试将数字以十六进制打印出来,看看是否更容易识别: std::cout << std::hex << i << std::endl; 而且始终记住要使用适当的“U”、“L”和/或“LL”限定符来修饰字面量: http://en.cppreference.com/w/cpp/language/integer_literal
unsigned long long l1 = 18446744073709550592ull;
unsigned long long l2 = 18'446'744'073'709'550'592llu;
unsigned long long l3 = 1844'6744'0737'0955'0592uLL;
unsigned long long l4 = 184467'440737'0'95505'92LLU;

1
我认为这取决于编译器。
在我的机器上(g ++),它给出相同的值
2147483648 2147483648
证明http://ideone.com/cvYzxN

如果存在溢出,那么因为uwantsumunsigned long int,而无符号值始终为正,所以使用(uwantsum)%2^64从有符号转换为无符号。

希望这可以帮助!


-2

它与您打印出来的方式有关。 使用格式说明符%lu应表示适当的长整型


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接