25

我在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

谁能告诉我这怎么可能?

4

5 回答 5

39
 255*256*256*256

所有操作数都是int你溢出的int。有符号整数的溢出是 C 和 C++ 中未定义的行为。

编辑:

请注意,255 << 24如果您的int类型是,则第二个声明中的表达式也会调用未定义的行为32-bit255 x (2^24)4278190080不能用 a 表示的32-bit int(最大值通常214748364732-bit int二进制补码表示的 a 上)。

C 和 C++ 都说E1 << E2如果E1是有符号类型并且是正数并且E1 x (2^E2)不能以 的类型表示E1,则程序会调用未定义的行为。这^是数学运算符。

于 2012-09-20T13:49:39.567 回答
17

你的文字是int. 这意味着所有的操作实际上都是在 上执行的int,并立即溢出。当转换为无符号 64 位 int 时,这个溢出的值就是您观察到的值。

于 2012-09-20T13:50:21.353 回答
16

也许值得解释一下产生数字 18446744073692774400 的原因。从技术上讲,您编写的表达式触发了“未定义的行为”,因此编译器可能会产生任何结果;但是,假设int是 32 位类型,现在几乎总是这样,如果你写,你会得到相同的“错误”答案

uint64_t x = (int) (255u*256u*256u*256u);

并且该表达式不会触发未定义的行为。(从unsigned intto的转换int涉及实现定义的行为,但由于多年来没有人生产过补码或符号和大小 CPU,因此您可能遇到的所有实现都以完全相同的方式定义它。)我已经写了以 C 风格进行转换,因为我在这里所说的一切都同样适用于 C 和 C++。

首先,让我们看一下乘法。我用十六进制写右手边,因为这样更容易看到发生了什么。

255u * 256u               = 0x0000FF00u
255u * 256u * 256u        = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)

最后一个结果 ,0xFF000000u具有 32 位数字集的最高位。因此,将该值转换为带符号的32 位类型会导致它变为负数,就好像从其中减去 2 32一样(这是我上面提到的实现定义的操作)。

(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216

我在那里写了十六进制数字,无u后缀,以强调当您将其转换为有符号类型时,该值的位模式不会改变;它只是被重新解释。

现在,当您将 -16777216 分配给uint64_t变量时,它会通过添加 2 64反向转换为无符号的 as-if 。(与无符号到有符号的转换不同,此语义是由标准规定的。)这确实改变了位模式,将数字的所有高 32 位设置为 1 而不是您预期的 0:

(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u

如果你用0xFFFFFFFFFF000000十进制写,你会得到 18446744073692774400。

作为最后一条建议,每当你从 C 或 C++ 中得到一个“不可能的”整数时,试着用十六进制打印出来;以这种方式更容易看到二进制补码固定宽度算术的奇怪之处。

于 2012-09-20T15:51:49.350 回答
0

答案很简单——溢出。

于 2012-09-21T03:29:59.293 回答
0

这里溢出发生在 int 上,当你将它分配给 unsigned int64 时,它转换为 18446744073692774400 而不是 4278190080

于 2012-09-21T05:19:32.903 回答