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.comg + + C + + 14和 -O2选项。

5975 次浏览

因为在使用 dimc进行索引之前要递增 di,所以第四次通过循环时将引用 mc [4] ,这已经超过了数组的末尾,这反过来又可能导致麻烦的行为。

这是由于未定义行为问题,在循环的最后一次迭代中访问数组 mc是出界的。一些编译器可能会在没有未定义行为的假设下执行激进的循环优化。其逻辑类似于下列情况:

  • 访问 mc的界限是未定义行为的
  • 不要有任何未定义行为
  • 因此 di < 4总是为真,否则 mc[di]将调用未定义行为

打开优化并使用 -fno-aggressive-loop-optimizations标志的 gcc 会导致无限循环行为消失(看看现场直播)。而 实例优化,但没有-fno-主动-循环优化表现出您所观察到的无限循环行为。

Godbolt 实时代码示例显示 di < 4检查被删除并替换为无条件 jmp:

jmp .L6

这几乎与 海湾合作委员会前4.8打破破规2006年基准中概述的情况完全相同。这篇文章的评论非常棒,非常值得一读。它指出,clang 在使用 -fsanitize=undefined的文章中捕捉到了这种情况,对于这种情况我无法重现,但是使用 -fsanitize=undefined的 gcc (译自: http://www.melpon.org/wandbox/permlink/(http://melpon.org/wandbox/permlink/BPetd3bysBlJQEy4))可以。也许最臭名昭著的错误周围的优化作出推论未定义行为是 取消 Linux 内核空指针检查

尽管这是一个激进的优化,但是需要注意的是,正如 C + + 标准所说的,未定义行为是:

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

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

允许的未定义行为 从完全忽略 结果难以预料的情况,到翻译过程中的行为,或者 以有记录的方式执行程序,这种方式具有环境的特点(有或没有发布 诊断信息) ,用于终止翻译或执行(通过发出诊断信息)

为了获得来自 gcc 的警告,我们需要将 cout移出循环,然后我们看到以下警告(译自: http://www.melpon.org/wandbox/permlink/(http://melpon.org/wandbox/permlink/pH497fr2MQ2astNY)) :

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版本说明中有记录。

你有这个:

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。 比较检查: 小于4吗? 是的,好的,继续。 增量 di 增加1。现在 di = 1。获取 mc []的“ nth”元素,并将其设置为 delta。这次我们抓取第2个元素,因为这个索引值是1而不是0。最后在 for 循环中执行代码块/s。

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

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

第4次迭代: 现在 di 设置为3。 比较检查: 是否小于4? 是并继续。 增量 di 增加1。现在 di = 4。(你知道这是怎么回事吗?)获取 mc []的“ nth”元素,并将其设置为 delta。这次我们抓取第5个元素,因为这个索引值是4。我们有个问题,我们的数组大小只有4。德尔塔现在有垃圾,这是未定义行为或腐败。最后使用“垃圾 delta”在 for 循环中执行代码块/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];
}