C++编译错误?

eiv*_*our 74 c++ gcc undefined-behavior

我有以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

我希望它输出"0,1,2,3"并停止,但它会输出一系列无穷无尽的"0,1,2,3,4,5 ......"

看起来比较di<4效果不好并且总是返回true.

如果我只是评论出来,delta=mc[di],我会像往常一样得到"0,1,2,3".无辜任务有什么问题?

我正在使用带有-O2选项的Ideone.com g ++ C++ 14.

Sha*_*our 107

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

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

打开优化的gcc并使用该-fno-aggressive-loop-optimizations标志会导致无限循环行为消失(请参见实时).虽然一个带有优化但没有-fno-aggressive-loop-optimizations的实例展示了你观察到的无限循环行为.

代号的一个Godbolt实例显示di < 4检查被删除并替换为和无条件的jmp:

jmp .L6
Run Code Online (Sandbox Code Playgroud)

这几乎与GCC 4.8之前的Breaks Broken SPEC 2006基准测试中概述的情况相同.对本文的评论非常好,非常值得一读.它注意到clang在文章中抓住了这个案例-fsanitize=undefined,我无法为这个案例重现,但是使用gcc -fsanitize=undefined(现场直播).可能是围绕未定义行为进行推理的优化器周围最臭名昭着的错误是Linux内核空指针检查删除.

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

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

这本质上意味着任何事情都是可能的,它注意到了(强调我的)

[...]允许的未定义行为包括完全忽略不完全结果的情况,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的文件化方式行事,终止翻译或执行(发布诊断信息).[...]

为了从gcc获得警告,我们需要移动cout循环外部,然后我们看到以下警告(请参见实时):

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

这可能足以为OP提供足够的信息来弄清楚发生了什么.像这样的不一致是我们可以通过未定义的行为看到的典型行为类型.为了更好地理解为什么这些waring在面对未定义的行为时会不一致为什么不能根据未定义的行为进行优化时发出警告?读起来不错.

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

  • 这个.未定义的行为不是(仅)有关崩溃的行为.这是关于假设的,如果你违反了编译器的假设(由语言规范定义),那么所有的赌注都会被取消...... (24认同)

Log*_*rat 38

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

  • 使用`di ++,delta = mc [di-1]`或`delta = mc [di],di ++`也解决了这个问题.看起来Logicrat是正确的. (2认同)
  • 也许有一个一个一个错误,eivour意味着`delta = mc [di ++]`而不是'delta = mc [++ di]`,使用所有`mc`值? (2认同)

Fra*_*ler 5

你有这个:

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

试试这个:

for(int di=0; di<4; delta=mc[di++]) {
   cout<<di<<endl;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

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

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

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

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

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

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

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


Pau*_*lHK 5

这是因为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)
Run Code Online (Sandbox Code Playgroud)

当di == 4时你正在访问mc [],所以它是一个超出范围的问题,可能会破坏堆栈的一部分并破坏变量di.

一个解决方案是:

for(int di = 0; di < 4; di++) {
    cout << di << endl;
    delta = mc[di];
}
Run Code Online (Sandbox Code Playgroud)