C++编译错误?

C++ compilation bug?

我有以下代码:

#include <iostream>
#include <complex>
using namespace std;

int main() {
    complex<int> delta;
    complex<int> mc[4] = {0};

    for(int di = 0; di < 4; di++, delta = mc[di]) {
        cout << di << endl;
    }

    return 0;
}

我希望它输出“0, 1, 2, 3”然后停止,但它输出了一个无穷无尽的系列“0, 1, 2, 3, 4, 5, .....”

看起来比较 di<4 效果不佳并且总是 returns 正确。

如果我只是注释掉 ,delta=mc[di],我会正常得到“0、1、2、3”。无辜的分配有什么问题?

我正在使用 Ideone.com g++ C++14 和 -O2 选项。

由于在使用它索引 mc 之前递增 di,第四次通过循环时,您将引用 mc[4],它已超出数组的末尾,这反过来可能会导致麻烦的行为。

这是由于未定义的行为,您在循环的最后一次迭代中越界访问数组 mc。一些编译器可能会围绕没有未定义行为的假设执行积极的循环优化。逻辑类似于以下内容:

  • 越界访问mc是未定义的行为
  • 假设没有未定义的行为
  • 因此 di < 4 始终为真,否则 mc[di] 将调用未定义的行为

打开优化并使用 -fno-aggressive-loop-optimizations 标志的 gcc 会导致无限循环行为消失(see it live). While a live example with optimization but without -fno-aggressive-loop-optimizations 表现出您观察到的无限循环行为。

A godbolt live example of the code 显示 di < 4 检查已删除并替换为无条件 jmp:

jmp .L6

这与 GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks. The comments to this article are excellent and well worth the read. It notes that clang caught the case in the article using -fsanitize=undefined which I can not reproduce for this case but gcc using -fsanitize=undefined does (see it live). Probably the most infamous bug around an optimizer making an inference around undefined behavior is the Linux kernel null pointer check removal 中概述的情况几乎相同。

虽然这是一个积极的优化,但重要的是要注意,正如 C++ 标准所说的未定义行为是:

behavior for which this International Standard imposes no requirements

这基本上意味着一切皆有可能,它指出(强调我的):

[...]Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).[...]

为了从 gcc 得到警告,我们需要将 cout 移到循环外,然后我们会看到以下警告 (see it live) :

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
     for(di=0; di<4;di++,delta=mc[di]){ }
     ^

这可能足以为 OP 提供足够的信息来弄清楚发生了什么。像这样的不一致是我们在未定义行为中可以看到的典型行为类型。为了更好地理解为什么面对未定义的行为时这种警告会不一致 Why can't you warn when optimizing based on undefined behavior? 是一本好书。

注意,-fno-aggressive-loop-optimizations 记录在 gcc 4.8 release notes

你有这个:

for(int di=0; di<4; di++, delta=mc[di]) {
  cout<<di<<endl;
}

试试这个:

for(int di=0; di<4; delta=mc[di++]) {
   cout<<di<<endl;
}

编辑:

为了弄清楚发生了什么,让我们分解一下 For 循环的迭代:

第 1 次迭代:最初 di 设置为 0。 比较检查:di 是否小于 4?是的,可以继续。 将 di 增加 1。现在 di = 1。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们获取第二个元素,因为这个索引值是 1 而不是 0。最后在 for 循环中执行代码 block/s。

第二次迭代:现在 di 设置为 1。 比较检查:di 是否小于 4?是的,继续。 将 di 增加 1。现在 di = 2。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们抓取第 3 个元素,因为这个索引值为 2。最后在 for 循环中执行代码 block/s。

第三次迭代:现在 di 设置为 2。 比较检查:di 是否小于 4?是的,继续。 将 di 增加 1。现在 di = 3。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们获取第 4 个元素,因为这个索引值为 3。 最后在for循环中执行代码block/s。

第 4 次迭代:现在 di 设置为 3。 比较检查:di 是否小于 4?是的,继续。 将 di 增加 1。现在 di = 4。(你能看出这是怎么回事吗?)抓住 mc[] 的 "nth" 元素并将其设置为 delta。这次我们要获取第 5 个元素,因为这个索引值为 4。呃哦,我们有问题;我们的数组大小只有 4。Delta 现在有垃圾,这是未定义的行为或损坏。最后使用 "garbage delta" 在 for 循环中执行代码 block/s。

第 5 次迭代。现在 di 设置为 4。 比较检查:di 是否小于 4?不,跳出循环。

由于超出连续内存(数组)的界限而损坏。

因为di++是在循环的最后运行处执行的。

例如;

int di = 0;
for(; di < 4; di++);
// after the loop di == 4
// (inside the loop we see 0,1,2,3)
// (inside the for statement, after di++, we see 1,2,3,4)

当 di == 4 时您正在访问 mc[],因此这是一个越界问题,可能会破坏堆栈的一部分并破坏变量 di。

一个解决方案是:

for(int di = 0; di < 4; di++) {
    cout << di << endl;
    delta = mc[di];
}