4

我最近在我的 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 行,首先计算括号,使用ints 作为中间结果,导致溢出并存储 -1073741824。这个中间结果被提升为long long最后的乘法,因此打印的结果应该是 -5368709120。
  • 第 1 行和第 3 行是“等价的”,因为评估的顺序是未定义的。

现在,对于第 1 行和第 3 行,我不确定:我认为尽管评估顺序未定义,但编译器会将所有操作“提升”为最大操作数的类型,即long long此处。因此,在这种情况下不会发生溢出,因为所有计算都将在 64b 中进行......但这就是 GCC 5.3.0 为我提供的代码:

~/tmp$ g++-5 cast.cc
~/tmp$ ./a.out 
-5368709120
-5368709120
16106127360

我也希望第一个结果是 16106127360。由于我怀疑 GCC 中是否存在这种规模的编译器错误,我猜这个错误位于键盘和椅子之间。

任何人都可以确认/确认这是未定义的行为,并且 GCC 给我的任何东西都是正确的(因为这是未定义的)?

4

2 回答 2

7

海湾合作委员会是正确的。

  1. 乘法的结合性是从左到右。这意味着所有这些表达式都是从左到右计算的。
  2. 提升到更高类型仅在不同类型的单个二元运算符的两个操作数之间。

例如,第一个表达式被解析为i * j * k * n * l = ((((i * j) * k) * n) * l),并且提升仅在计算最后一个乘法时发生,但此时左操作数已经不正确。

于 2016-01-15T13:12:54.970 回答
3

标准明确定义分组如下:

5.6 乘法运算符 [expr.mul]

1 乘法运算符 *、/ 和 % 从左到右分组。

这意味着a * b * c被评估为(a * b) * c。符合标准的编译器没有自由将其评估为a * (b * c).

于 2016-01-15T13:13:44.530 回答