C ++编译错误?

我有以下代码:

#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不能很好地工作,并且总是返回true。

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

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


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

  • 访问mc越界是未定义的行为
  • 假定没有未定义的行为
  • 因此di < 4总是为真,否则mc[di]会调用未定义的行为
  • gcc打开优化,并使用-fno-aggressive-loop-optimizations标志导致无限循环行为消失(见它现场)。 虽然有一个优化但没有-fno-aggressive-loop-optimizations的实例展示了你观察到的无限循环行为。

    代码的godbolt现场示例显示, di < 4检查被删除并被替换为并且无条件jmp:

    jmp .L6
    

    这与GCC 4.8以前破解SPEC SPEC Bench Benchmarks中概述的情况几乎相同。 这篇文章的评论非常好,值得一读。 它注意到clang在文章中使用-fsanitize=undefined捕获了这个案例,我不能为这个案例重现,但是gcc使用-fsanitize=undefined (看它现场)。 围绕未定义行为进行推理的优化器可能是臭名昭着的bug,它是Linux内核空指针检查删除。

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

    本国际标准对此没有要求的行为

    这基本上意味着任何事情都是可能的,并注意到(强调我的):

    [...]允许的未定义行为的范围从完全忽略情况而产生不可预知的结果 ,在翻译或程序执行过程中以环境特征(有或没有发布诊断消息)的文件化方式行事,终止翻译或执行(通过发布诊断信息)。

    为了从gcc获得警告,我们需要将cout移到循环外部,然后看到以下警告(请参阅它的实况):

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

    这可能足以为OP提供足够的信息来弄清楚发生了什么。 这种不一致是典型的行为类型,我们可以看到未定义的行为。 为了更好地理解为什么在未定义的行为面前这样的行为可能会不稳定为什么不能在基于未定义的行为进行优化时发出警告? 是一个很好的阅读。

    请注意,gcc 4.8发行说明中记录了-fno-aggressive-loop-optimizations


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


    你有这个:

    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循环迭代:

    第一次迭代:最初di设置为0.比较检查:di小于4? 好的,继续。 将di递增1.现在di = 1。获取mc []的“第n个”元素并将其设置为delta。 这次我们抓取第二个元素,因为这个索引值是1而不是0.最后在for循环中执行代码块/ s。

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

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

    第4次迭代:现在di被设置为3.比较检查:是小于4吗? 是,继续。 将di增加1.现在di = 4.(你能看出这是怎么回事?)抓住mc []的“第n个”元素并将其设置为delta。 这次我们抓取了第五个元素,因为这个索引值是4.呃,我们有一个问题; 我们的数组大小仅为4.达美现在有垃圾,这是未定义的行为或损坏。 最后使用“垃圾三角洲”在for循环中执行代码块/ s。

    第5次迭代。 现在迪设置为4.比较检查:迪小于4? 不,打破循环。

    通过超出连续内存(数组)的腐败。

    链接地址: http://www.djcxy.com/p/86819.html

    上一篇: C++ compilation bug?

    下一篇: Why do neither move semantics nor RVO work as expected?