C++中long long存储int乘积溢出与强转后正常的原因
为什么用long long接收结果还是会溢出
你写的第一段溢出代码如下:
int a = 1000000000, b = 1000000000; long long int ans = a * b; cout << ans << '\n';
这里踩中了C++类型系统的一个基础规则:表达式的求值类型,仅由参与运算的操作数本身类型决定,和最终赋值的目标变量类型完全无关。a和b都声明为int类型,因此a * b这个乘法运算全程以int类型规则计算。绝大多数环境下int是32位有符号整数,能表示的最大值仅为2^31-1=2147483647,而两个1e9相乘结果是1e18,远超出这个范围,乘法执行的瞬间就已经发生整数溢出,属于未定义行为。等错误的溢出结果计算完成后,编译器才会把这个已经出错的值转换成long long类型存入ans,此时再做类型转换根本无法挽回已经发生的溢出错误。
为什么强制转换后代码可以正常运行
第二段可正常运行的代码如下:
int a = 1000000000, b = 1000000000; long long int ans = (long long int)a * b; cout << ans << '\n';
这段代码里通过强制类型转换,先把a转换成了long long int类型。C++的二元运算有明确的类型提升规则:当运算符两边操作数类型不一致时,会自动把表示范围更小的操作数提升为范围更大的类型,两边类型统一后再做计算。
因此这个乘法表达式里,b会被自动提升为long long int类型,整个乘法运算全程以64位长整型规则计算——64位有符号整数可以表示的最大值为2^63-1≈9e18,完全可以容纳1e18的计算结果,自然不会发生溢出。
关于中间临时值的问题
计算过程中确实会生成编译器管理的临时值:强制转换后的a、自动提升后的b、以及乘法得到的中间结果,都是临时存储的值(一般放在寄存器或者栈的临时内存区域),等整个表达式求值完成后,最终的计算结果才会被赋值给ans变量。
很多人会误以为赋值目标的类型会影响前面的计算逻辑,这是非常常见的认知误区:赋值是表达式求值完成后的最后一步,不会反向改变子表达式的计算规则。
内容的提问来源于stack exchange,提问作者Jahirul Sarker

