整数铸造错误?

时间:2013-01-15 19:41:03

标签: c++ casting integer integer-overflow

请考虑以下代码:

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

我预计以下两个陈述都不会产生正确的结果,一个人怎么成功而另一个没有?

5 个答案:

答案 0 :(得分: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 的位代表无关。

没有“位转换”,编译器只是将这些位视为无符号数量。转化与一起使用。转换为signed int的{​​{1}}的在§4.7/ 2中定义:

  

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

对于我们的32位(unsigned intunsigned系统,这意味着4000000000.无论位是什么都有效:恭维,恭维,魔术恭维等等。不相关的。

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

但是铸造工作独立于钻头!

答案 1 :(得分:3)

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

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

看看这两个,你可以看到它们是一样的。

区别在于intunsigned int中的位读取方式。在常规int most significant bit中,{{3}}用于判断数字是否为负数。

答案 2 :(得分:1)

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

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

将其投放到int不会更改该值,因为long long可以代表-294967296就好了。

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

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

unsinged int

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

答案 3 :(得分: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会产生一个字节序列,然后它被解释为强制转换操作。

答案 4 :(得分:0)

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


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

warning: integer overflow in expression [-Woverflow]

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

但是,如果将值转换为更大宽度的整数类型(例如64位),则前导字节将填充ffsign extension),从而产生错误结果。< / p>

#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

}
相关问题