C++ 中的整数上溢和下溢

0 c++ integer integer-overflow undefined-behavior modular-arithmetic

任何人都可以解释为什么会发生这种情况吗?:

int a = 2147483647;
cout <<"Product = " << a * a << endl; // output = 1 (why?)
int b = -2147483648;
cout <<"Product = " << b * b << endl; // output = 0 (why?)
Run Code Online (Sandbox Code Playgroud)

此外,当我们为 'short' 编写类似的内容时,编译器将乘积视为整数类型,尽管变量被初始化为短类型,例如:

short x = 32767;
cout <<"Product = " << x * x << endl; // Product = 1073676289
short y = -32768;
cout <<"Product = " << y * y << endl;// Product = 1073741824
Run Code Online (Sandbox Code Playgroud)

ein*_*ica 8

第 1 部分:直觉,无需查阅参考文献

让我们尝试使用一些直觉或合理的假设来解释为什么这些结果是有意义的。

基本上,这是关于物理整数变量可以表示的内容 - 这不是所有整数的整个无限集,而是范围 -2^31 ... 2^31 - 1 (对于 32 位整数,这是可能是你拥有的;但一定要检查以确保,例如使用sizeof()。)

现在,如果

a = 2^31 - 1
Run Code Online (Sandbox Code Playgroud)

然后

a*a = (2^31 - 1)^2 = 2^62 - 2*2^31 + 1 = 2^62 - 2^32 + 1
Run Code Online (Sandbox Code Playgroud)

作为理想整数,并忽略物理表示。当将结果放入 32 位整数时,合理的猜测是 CPU 给出的任何结果都将与理想结果等效(模 2^31 或 2^32):

  (2^62 - 2^32 + 1) mod 2^32
= (2^62 mod 2^32) - (2^32 mod 2^32) + (1 mod 2^32) 
= 1 mod 2^32
Run Code Online (Sandbox Code Playgroud)

这就是你得到的。

对于b,您有:

b = -2147483648 = -2^31
Run Code Online (Sandbox Code Playgroud)

b*b = 2^62
Run Code Online (Sandbox Code Playgroud)

相当于 0 对 2^31 或 2^32 取模。


第 2 部分:语言的要求

虽然我们的直觉确实对结果给出了合理的解释,但考虑“官方”答案通常也很有用。在我们的例子中,我引用cppreference.com(这不是官方语言标准,但足够接近):

当有符号整数算术运算溢出(结果不适合结果类型)时,行为是未定义的:它可能根据表示规则(通常是 2 的补码)回绕,它可能在某些平台上或由于编译器而陷入困境选项(例如 GCC 和 Clang 中的 -ftrapv),或者可能被编译器完全优化。

所以实际上,你不能保证在不同的编译器、不同的编译器版本、不同的机器上甚至在重复编译相同的代码时获得这些值!实际上你可以得到 1234 和 5678 作为结果,你不能抱怨。否则你的程序可能会崩溃。或者可能发生其他任何事情!看:

未定义、未指定和实现定义的行为

关于问题的第二部分(关于 s short) - 这是由于整数提升所致:

以下算术运算符的参数经过隐式转换,以获得公共实数类型,即执行计算的类型:

  • 二进制算术 *、/、%、+、-

...

整数提升是将任何等级小于或等于 int 等级或 ... [此处为其他类型] 类型的位字段的整数类型的值隐式转换为类型或short的值intunsigned int

第 3 部分:实际底线

注意避免计算值超过std::numeric_limits<T>::max():使用较大的类型开始,或检查是否过高的值(例如,确保两个值的绝对值都低于 2^16,否则应用一些特殊处理)。