2

考虑以下代码:

#include <iostream>
using namespace std;

int main() {
    // the following is expected to not print 4000000000
    // because the result of an expression with two `int`
    // returns another `int` and the actual result 
    // doesn't fit into an `int` 
    cout << 2 * 2000000000 << endl; // prints -294967296

    // as such the following produces the correct result
    cout << 2 * 2000000000U << endl; // prints 4000000000
}

我玩了一下将结果转换为不同的整数类型,并遇到了一些奇怪的行为。

#include <iostream>
using namespace std;

int main() {
    // unexpectedly this does print the correct result
    cout << (unsigned int)(2 * 2000000000) << endl; // prints 4000000000

    // this produces the same wrong result as the original statement
    cout << (long long)(2 * 2000000000) << endl; // prints -294967296
}

我预计以下两个语句都不会产生正确的结果,为什么一个成功而另一个没有?

4

5 回答 5

4

在试图回答这个问题的人们中发生了太多的困惑。

让我们检查一下:

2 * 2000000000

这是一个int乘以一个int。§5/4 告诉我们:

如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。

这个结果是在数学上定义的,但它是否在 的可表示值范围内int

那要看。在许多常见架构int上,有 32 位来表示值,最大值为 2,147,483,647。由于其数学结果是 4,000,000,000,因此这样的架构将无法表示该值并且行为未定义。(这几乎解决了这个问题,因为现在整个程序的行为是未定义的。)

但这仅取决于平台。如果int改为 64 位宽(注意:long long保证至少有 64 位来表示值),结果会很好。

让我们稍微解决一下这个问题,然后直奔主题:

int x = -294967296; // -294,967,296

让我们进一步说这符合int(对于 32 位int它确实如此)的范围。

现在让我们将其转换为unsigned int

unsigned int y = static_cast<unsigned int>(x);

的价值是y多少?它与 的位表示无关x

没有“位转换”,编译器只是将位视为无符号数量。转换与一起使用。a转换为 an的在 §4.7/2 中定义:signed intunsigned int

如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模 2 n其中 n 是用于表示无符号类型的位数)。[注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。——尾注]

对于我们在 32 位 ( unsigned)int系统上的我们来说,这意味着 4000000000。这与位无关:two's-compliment、one's-compliment、magic's-compliment 等。这些是无关紧要的。

您在第一个地方看到您想要的值(忽略 UB)的原因是,在您的二进制恭维机器上,有符号整数和无符号整数之间的差异确实是不同查看位的问题。因此,当您将这两个 相乘时int,您“实际上”将两个无符号整数相乘,忽略了溢出,并将结果视为有符号整数。然后演员再次改变你的看法。

但是铸造独立于位!

于 2013-01-15T20:43:29.973 回答
3

在 int 中, 的值4,000,000,000写为1110 1110 0110 1011 0010 1000 0000 0000

在无符号整数中, 的值4,000,000,000写为1110 1110 0110 1011 0010 1000 0000 0000

查看这两个,您可以看到它们是相同的。

int不同之处在于在 a和中读取位的方式unsigned int。在常规int中,最高有效位用于判断数字是否为负。

于 2013-01-15T19:46:36.583 回答
1

在 C++ 中,表达式的类型(通常)不依赖于代码环境。

因此,子表达式 2 * 2000000000 在同一系统上具有相同的类型和值,无论包含表达式的上下文是什么int(因为 * 运算符的两个操作数都是ints)。它会是 4000000000,但是在您的架构上,由于溢出,它更改为 -294967296。

将其转换为long long不会改变值,因为long long可以代表 -294967296 就好了。

实际上,它更有趣cout << (unsigned int)(2 * 2000000000) << endl;。由于unsinged int无法容纳-294967296,再次发生溢出。-294967296 和 4000000000 模 2^32 是全等的,所以这将是新值。(从 GManNickG 的更好答案更新)。

为了说明更深层次的问题,您可以尝试

cout << (unsigned int)(2 * 2000000000 / 2) << endl;

除法将在 -294967296 上执行,-147483648 的二进制表示将转换为无符号数,即 4147483648

于 2013-01-15T19:49:32.870 回答
0

在第三种(奇怪的)情况下,正在运行的程序执行以下操作:

2 * 2000000000       = binary number (11101110011010110010100000000000)
print it as unsigned = 4000000000 
                   (interprets the first bit (1) as part of the unsigned number)

第四种情况:

2 * 2000000000       = binary number (11101110011010110010100000000000, same as above) 
print it as signed   = -294967296 
                   (interprets the first bit (1) as negative number)

要学习的重要一点是表达式 2 * 2000000000 产生一个字节序列,然后将其解释为强制转换操作所说的。

于 2013-01-15T19:57:52.587 回答
0

请注意,有符号整数溢出是未定义的行为。总而言之,任何事情都可能发生。包括无辜正确的结果。


整数文字2200000000032 位宽。结果将溢出,正如您的编译器告诉您的那样:

warning: integer overflow in expression [-Woverflow]

乘法的结果仍然是一个 32 位有符号整数。而且,在这种情况下,幸运的是,溢出的结果是正确的结果,当被视为无符号 32 位整数时。您可以在将位模式转换为 32bit 时观察到这一点unsigned int

但是,如果将值转换为更大宽度的整数类型(例如 64 位),则前导字节将用ff(符号扩展) 填充,从而给出错误的结果。

#include <iostream>

int main() {
    long long x = 2 * 2000000000;     // 8 byte width
    unsigned int y = 2 * 2000000000;  // 4 byte width
    unsigned long z = 2 * 2000000000; // 8 byte width
    std::cout << std::hex << x << " " << std::dec << x << std::endl;
    // output is: ffffffffee6b2800 -294967296
    std::cout << std::hex << y << " " << std::dec << y << std::endl;
    // output is: ee6b2800 4000000000
    std::cout << std::hex << z << " " << std::dec << z << std::endl;
    // output is: ffffffffee6b2800 18446744073414584320

}
于 2013-01-15T20:07:31.043 回答