C ++编译错误?


74

我有以下代码:

#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”。无辜的任务有什么问题?

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


5
我已经使用g ++进行了尝试,并且没有优化就可以正常工作。使用-O3可以给出OP提到的行为。使用-O1可以。
克里斯·卡


尽管代码确实调用了未定义的行为,但是优化相当激进,而且正如我所观察到的那样,gcc不能始终如一地为该优化提供警告也无济于事。
Shafik Yaghmour 2015年

2
@Shafik Yaghmour GCC不提供警告的cout << di原因可能是复杂的流插入运算符将地址传递di给某些“不透明”代码(或者复杂的流插入运算符本身是不透明的-这会让我感到惊讶虽然)。并且,根据“不透明”代码的作用,仍可以很好地定义程序的行为。我并不是说在这种情况下,如果没有太多的误报(甚至任何误报),就不可能提供警告。只是那将是相当困难的。
Paul Groke 2015年

@ShafikYaghmour我正在使用带有-O2选项的Ideone.com g ++ C ++ 14。(谢谢,我在问题中添加了它。)
eivour 2015年

Answers:


108

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

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

启用了优化功能的gcc并使用该-fno-aggressive-loop-optimizations标志会导致无限循环行为消失(参见live)。虽然有优化但没有-fno-aggressive-loop-optimizations实时示例展示了您观察到的无限循环行为。

的代码godbolt活示例示出了di < 4检查移除并用无条件JMP代替:

jmp .L6

这几乎与GCC 4.8之前的Breaks Breaked SPEC 2006 Benchmarks中概述的情况相同。对本文的评论非常好,值得一读。它注意到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]){ }
     ^

这可能足以为OP提供足够的信息,以找出正在发生的事情。像这样的不一致是我们在未定义行为中可以看到的典型行为类型。更好地了解为什么在面对未定义行为时这种警告会变得不一致,为什么在基于未定义行为进行优化时不发出警告?是一本好书。

注意,-fno-aggressive-loop-optimizationsgcc 4.8发行说明中有记录


25
这个。未定义的行为不仅仅与崩溃有关。这是关于假设的,如果您违反了编译器的假设(如语言规范所定义),那么所有赌注都将
失效

@MatthieuM。确实,我们一直回到那个主题,我在这里回答中的一些引号也是相关的。
Shafik Yaghmour 2015年

我真是个傻瓜,我没有注意到我正在访问mc [4];)我使用的是Ideone.com,因此无论如何我都不会收到警告。下次,我将使用即使编译成功也向我发出警告的编辑器:)
eivour

@eivour提供指向您的在线示例的链接可能会有所帮助,在这种情况下,我认为每次您在ideone中运行示例时,它都会提供该示例的URL。我个人更喜欢使用均提供共享按钮的ColiruWandbox
Shafik Yaghmour 2015年

gcc 4.9.3仍然不会产生任何警告:g++ -Waggressive-loop-optimizations -Wall -Wextra -O3 test.cpp-根据此页面,编译器应显示警告:gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
Peter VARGA

38

由于di在使用索引进行递增之前要进行递增操作mc,因此在循环中第四次引用的是mc [4],它已超出数组的末尾,从而可能导致麻烦的行为。


忽略我的最后一条评论,这可能是ideone.com在编辑后无法正确地重新运行我的代码的问题。另一个测试成功了:ideone.com/vLtvcy
interjay 2015年

2
使用di++,delta=mc[di-1]delta=mc[di],di++还可以解决问题。看起来Logicrat是正确的。
KompjoeFriek 2015年

2
也许存在一个错误的错误,并且eivour是要使用所有值delta=mc[di++]而不是?delta=mc[++di]mc
Toby Speight

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)

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

一个解决方案是:

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

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 []的“ nth”元素并将其设置为delta。这次我们抓取第二个元素,因为该索引值为1而不是0。最后在for循环内执行代码块。

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

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

第4次迭代:现在di设置为3。比较检查:di小于4吗?是的,继续。将di递增1。现在di =4。(您知道它的去向吗?)抓住mc []的“ nth”元素并将其设置为delta。这次我们要抓取第5个元素,因为该索引值为4。我们的数组大小只有4。Delta现在有垃圾,这是未定义的行为或损坏。最后,使用“垃圾增量”在for循环内执行代码块。

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

通过超出连续内存(数组)的范围来进行损坏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.