请考虑以下代码:
#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
}
我预计以下两个陈述都不会产生正确的结果,一个人怎么成功而另一个没有?
答案 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 int
)unsigned
系统,这意味着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
看看这两个,你可以看到它们是一样的。
区别在于int
和unsigned 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)
请注意,有符号整数溢出是未定义的行为。总之,任何事情都可能发生。包括无辜正确的结果。
整数文字2
和2000000000
都是32位宽。结果将溢出,正如您的编译器告诉您的那样:
warning: integer overflow in expression [-Woverflow]
乘法的结果仍然是32位有符号整数。并且,在这种情况下,当被视为无符号32位整数时,溢出的结果幸运地是正确的结果。将位模式转换为32位unsigned int
时,您可以观察到这一点。
但是,如果将值转换为更大宽度的整数类型(例如64位),则前导字节将填充ff
(sign 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
}