不同版本的gcc以不同的方式编译相同的代码

Different versions of gcc differently compile the same code

本文关键字:代码 编译 gcc 版本 方式      更新时间:2023-10-16

我已经从MS Visual Studio切换到gcc,目前我正在尝试通过gcc重新编译我在VS中编写的一些代码。现在我遇到了一些奇怪的事情。简单地解释一下,考虑下面的代码,但首先,请注意,我已经知道这是一个非常糟糕的代码(这不是这里的重点)

#include <iostream>
int main()
{
    int i = 0,
        a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
        b[10] = { 6, 5, 4, 1, 3, 2, 9, 7, 10, 8 };
    while (i ^ 5)   a[i++] = a[i] + b[i];
    while (i ^ 10)  a[i++] = a[i] - b[i];
    for (int j = 0; j < 10; j++)
        std::cout << a[j] << ' ';
}

当我用Visual Studio编译它时,结果是:

7 7 7 5 8 4 -2 1 -1 2 

正如预期的那样。对于gcc v.4.3.6,我也得到了相同的结果(Live示例)。

但当我切换到gcc 5.3.0时,结果是:

7 7 5 8 8 -2 1 -1 2 -4198061

在生成许多关于未定义行为的警告之后。

问题是,为什么visualstudio,即使是在其最新版本中,也不关心代码的质量和未定义的行为,以及为什么早期版本的gcc也这样做?最近版本的gcc发生了什么?

本主题在C++11标准的§1.9/15(程序执行)中进行了讨论:

除非另有说明,否则单独运算符的操作数和单独表达式的子表达式的求值都是无序列的。[注意:在执行过程中多次求值的表达式中对于一个程序,其子表达式的未排序和不确定排序的求值不需要在不同的求值中一致执行。——尾注]在运算符的结果的值计算之前对运算符进行排序。如果标量对象上的副作用相对于同一标量对象上另一个副作用或值计算未排序使用相同标量对象的值,并且它们不可能并发(1.10),行为未定义。。。

void g(int i, int* v) {
    i = v[i++];       // the behavior is undefined
    i = 7, i++, i++;  // i becomes 9
    i = i++ + 1;      // the behavior is undefined
}

未定义的行为意味着:任何事情都可能发生,程序可能会按照你的预期运行,或者可能会发生"奇怪"的事情。

另请参阅:维基百科上的"序列点":

根据表达式求值的顺序,增量可能发生在赋值之前、之后或与赋值交错。。。

一个快速的解决办法是改变

while (i ^ 5)   a[i++] = a[i] + b[i];

while (i ^ 5)   a[i] = a[i] + b[i], i++;

这行代码在我看来是未定义的行为:

a[i++] = a[i] + b[i];

这可能意味着:

a[i] = a[i] + b[i];
i++;

或者:

a[i] = a[i + 1] + b[i + 1];
i++;

第一个编译器使用第一种解释,而第三个编译器使用第二种解释。

'当(i^5)a[i++]'-i修改了两次时,没有序列点,所以UB。

'在对&amp;(逻辑AND),||(逻辑OR)'—未提及XOR。

也许。。。。。。。