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]
将调用未定义的行为
海湾合作委员会打开优化,并使用-fno-aggressive-loop-optimizations
标志导致无限循环行为消失( 见现场 )。 虽然一个优化但没有-fno-aggressive-loop-optimizations的实例展示了你所观察到的无限循环行为。
代码的godbolt生活示例显示di < 4
检查被删除,并被replace为并且无条件的jmp:
jmp .L6
这与GCC 4.8以前版本的“打破SPEC 2006基准testing”中概述的情况几乎相同。 这篇文章的评论非常好,值得一读。 它注意到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提供足够的信息来弄清楚发生了什么事情。 像这样的不一致是典型的行为types,我们可以看到未定义的行为。 为了更好地理解为什么在未定义的行为面前这样的行为可能会不稳定为什么不能根据未定义的行为来进行优化呢? 是一个很好的阅读。
请注意, gcc 4.8发行说明中logging了-fno-aggressive-loop-optimizations
。
既然你在递增di
之前你用它来索引mc
,第四次通过循环你将会引用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; }
编辑:
澄清是怎么回事让我们打破你的循环迭代:
第一次迭代:最初di被设置为0.比较检查:是否小于4? 好的,继续。 将di递增1.现在di = 1。获取mc []的“nth”元素并将其设置为delta。 这次我们抓取第二个元素,因为这个索引值是1,而不是0.最后在for循环中执行代码块/ s。
第二次迭代:现在di被设置为1.比较检查:di小于4吗? 是,继续。 将di递增1.现在di = 2。获取mc []的“n”个元素并将其设置为delta。 这次我们抓取第三个元素,因为这个索引值是2.最后在for循环中执行代码块/ s。
第3次迭代:现在di被设置为2.比较检查:是否小于4? 是,继续。 将di递增1.现在di = 3.获取mc []的“n”个元素并将其设置为delta。 这次我们抓取了第4个元素,因为这个索引值是3.最后在for循环中执行代码块/ s。
第4次迭代:现在di设置为3.比较检查:di小于4? 是,继续。 将di增加1.现在di = 4.(你可以看到这是怎么回事?)抓住mc []的“第n个”元素并将其设置为delta。 这次我们抓取了第五个元素,因为这个索引值是4.呵呵,我们有一个问题, 我们的数组大小只有4.达美现在有垃圾,这是未定义的行为或腐败。 最后使用“垃圾三angular洲”在for循环中执行代码块/ s。
第五次迭代。 现在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 [],所以这是一个超出界限的问题,可能会破坏堆栈的一部分并破坏variablesdi。
解决scheme将是:
for(int di = 0; di < 4; di++) { cout << di << endl; delta = mc[di]; }