整数溢出和运算顺序
Integer overflow and order of operations
我最近遇到了一个关于我的 C++ 代码的问题,这让我想知道我是否对编译器对长操作的处理有一些误解...
看看下面的代码:
#include <iostream>
int main() {
int i = 1024, j = 1024, k = 1024, n = 3;
long long l = 5;
std::cout << i * j * k * n * l << std::endl; // #1
std::cout << ( i * j * k * n ) * l << std::endl; // #2
std::cout << l * i * j * k * n << std::endl; // #3
return 0;
}
对我来说,这 3 行中任何一行的乘法运算顺序是不确定的。然而,这是我认为会发生的事情(假设 int
是 32b,long long
是 64b 并且它们都遵循 IEEE 规则):
- 对于第 2 行,首先计算括号,使用
int
s 作为中间结果,导致溢出并存储 -1073741824。对于最后一次乘法,此中间结果被提升为 long long
,因此打印结果应为 -5368709120.
- 第 1 行和第 3 行是 "equivalent",因为计算顺序未定义。
现在,对于第 1 行和第 3 行,我不确定:我认为虽然计算顺序未定义,但编译器会将所有操作 "promote" 设为最大操作数的类型,即这里的long long
。因此,在这种情况下不会发生溢出,因为所有计算都将在 64b 中进行...但这就是 GCC 5.3.0 为这段代码提供的内容:
~/tmp$ g++-5 cast.cc
~/tmp$ ./a.out
-5368709120
-5368709120
16106127360
我本以为第一个结果也是 16106127360。由于我怀疑 GCC 中是否存在如此严重的编译器错误,我猜错误就在键盘和椅子之间。
任何人都可以确认/确认这是未定义的行为,GCC 给我的任何东西都是正确的(因为这是未定义的)?
海湾合作委员会是正确的。
- 乘法结合律是left to right。这意味着所有这些表达式都是从左到右求值的。
- 仅在 单个 二元运算符的不同类型的两个操作数之间升级到更高类型。
例如,第一个表达式被解析为 i * j * k * n * l = ((((i * j) * k) * n) * l)
并且仅在计算最后一个乘法时才进行提升,但此时左操作数已经不正确。
标准明确定义分组如下:
5.6 Multiplicative operators [expr.mul]
1 The multiplicative operators *, /, and % group left-to-right.
这意味着 a * b * c
被评估为 (a * b) * c
。符合标准的编译器没有自由将其评估为 a * (b * c)
。
我最近遇到了一个关于我的 C++ 代码的问题,这让我想知道我是否对编译器对长操作的处理有一些误解... 看看下面的代码:
#include <iostream>
int main() {
int i = 1024, j = 1024, k = 1024, n = 3;
long long l = 5;
std::cout << i * j * k * n * l << std::endl; // #1
std::cout << ( i * j * k * n ) * l << std::endl; // #2
std::cout << l * i * j * k * n << std::endl; // #3
return 0;
}
对我来说,这 3 行中任何一行的乘法运算顺序是不确定的。然而,这是我认为会发生的事情(假设 int
是 32b,long long
是 64b 并且它们都遵循 IEEE 规则):
- 对于第 2 行,首先计算括号,使用
int
s 作为中间结果,导致溢出并存储 -1073741824。对于最后一次乘法,此中间结果被提升为long long
,因此打印结果应为 -5368709120. - 第 1 行和第 3 行是 "equivalent",因为计算顺序未定义。
现在,对于第 1 行和第 3 行,我不确定:我认为虽然计算顺序未定义,但编译器会将所有操作 "promote" 设为最大操作数的类型,即这里的long long
。因此,在这种情况下不会发生溢出,因为所有计算都将在 64b 中进行...但这就是 GCC 5.3.0 为这段代码提供的内容:
~/tmp$ g++-5 cast.cc
~/tmp$ ./a.out
-5368709120
-5368709120
16106127360
我本以为第一个结果也是 16106127360。由于我怀疑 GCC 中是否存在如此严重的编译器错误,我猜错误就在键盘和椅子之间。
任何人都可以确认/确认这是未定义的行为,GCC 给我的任何东西都是正确的(因为这是未定义的)?
海湾合作委员会是正确的。
- 乘法结合律是left to right。这意味着所有这些表达式都是从左到右求值的。
- 仅在 单个 二元运算符的不同类型的两个操作数之间升级到更高类型。
例如,第一个表达式被解析为 i * j * k * n * l = ((((i * j) * k) * n) * l)
并且仅在计算最后一个乘法时才进行提升,但此时左操作数已经不正确。
标准明确定义分组如下:
5.6 Multiplicative operators [expr.mul]
1 The multiplicative operators *, /, and % group left-to-right.
这意味着 a * b * c
被评估为 (a * b) * c
。符合标准的编译器没有自由将其评估为 a * (b * c)
。