我在c++下编程时遇到了一件奇怪的事情。这是一个简单的乘法运算。
代码:
unsigned __int64 a1 = 255*256*256*256;
unsigned __int64 a2= 255 << 24; // same as the above
cerr()<<"a1 is:"<<a1;
cerr()<<"a2 is:"<<a2;
有趣的是,结果是:
a1 is: 18446744073692774400
a2 is: 18446744073692774400
而它应该是:(使用计算器确认)
4278190080
有人能告诉我这怎么可能吗?
255*256*256*256
所有操作数都是int
,您正在溢出int
。有符号整数的溢出在C和C++中是未定义的行为。
编辑:
请注意,如果您的int
类型是32-bit
,那么第二个声明中的表达式255 << 24
也会调用未定义的行为。255 x (2^24)
是不能在32-bit
int
中表示的4278190080
(在二的补码表示中,最大值通常是32-bit
int
上的2147483647
)。
对于E1 << E2
,C和C++都表示,如果E1
是有符号类型且为正,并且E1 x (2^E2)
不能用E1
的类型表示,则程序调用未定义的行为。这里^
是数学上的幂算子。
您的文字是int
。这意味着所有的操作实际上都在int
上执行,并且迅速溢出。当转换为无符号64位int时,这个溢出的值就是您观察到的值。
也许值得解释一下产生数字18446744073692774400的原因。从技术上讲,您编写的表达式会触发"未定义的行为",因此编译器可能会产生任何;然而,假设int
是32位类型,现在几乎总是这样,如果你写,你会得到同样的"错误"答案
uint64_t x = (int) (255u*256u*256u*256u);
并且该表达式不触发未定义的行为。(从unsigned int
到int
的转换涉及实现定义的行为,但由于多年来没有人产生一个一补或正负号CPU,您可能遇到的所有实现都以完全相同的方式定义它。)我用C风格编写了转换,因为我在这里所说的一切都同样适用于C和C++。
首先,让我们来看看乘法。我用十六进制写右手边,因为这样更容易看到发生了什么。
255u * 256u = 0x0000FF00u
255u * 256u * 256u = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)
最后一个结果0xFF000000u
具有32位数字集中的最高位。因此,将该值强制转换为有符号的32位类型会使其变为负值,就好像从中减去了232一样(这就是我上面提到的实现定义的操作)。
(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216
我在那里写了十六进制数字,没有u
后缀,以强调当您将值转换为有符号类型时,值的位模式不会改变;它只是被重新解释。
现在,当您将-16777216分配给uint64_t
变量时,它会被反转换为无符号,就像添加264一样。(与无符号到有符号的转换不同,这种语义是由标准规定的。)这个确实改变了比特模式,将数字的所有高32位设置为1,而不是如您所期望的0:
(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u
如果你用十进制写0xFFFFFFFFFF000000
,你会得到18446744073692774400。
最后一条建议是,每当你从C或C++中得到一个"不可能"的整数时,试着用十六进制打印出来;通过这种方式,可以更容易地看到二的奇数与固定宽度算术的互补。
答案很简单——溢出。
此处int发生溢出,当您将其分配给无符号int64时,它转换为18446744073692774400,而不是4278190080