c++编译错误

C++ compilation bug?

本文关键字:错误 编译 c++      更新时间:2023-10-16

我有以下代码:

#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-优化的实例显示了您所观察到的无限循环行为。

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

jmp .L6

这几乎与GCC 4.8之前的破损SPEC 2006基准中概述的情况相同。对这篇文章的评论非常好,非常值得一读。它注意到clang在文章中使用-fsanitize=undefined捕获了这种情况,我无法为这种情况复制这种情况,但gcc使用-fsanitize=undefined可以(查看现场)。关于优化器对未定义行为进行推断的最臭名昭著的错误可能是Linux内核空指针检查删除。

虽然这是一个激进的优化,但重要的是要注意,正如c++标准所说,未定义的行为是:

本国际标准未规定的行为

本质上意味着一切皆有可能,它注意到(强调mine):

[…允许的未定义行为范围从完全忽略情况导致不可预测的结果,到在翻译或翻译过程中的行为以环境特征的文档化方式执行程序(有或没有发出(诊断消息),以终止翻译或执行(通过发出诊断消息)。[…]

为了从gcc获得警告,我们需要将cout移出循环,然后我们看到以下警告(see it live):

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

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

注意,-fno-aggressive-loop-optimizations在gcc 4.8发行说明中有文档记录。

由于您在使用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;
}
编辑:

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

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

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

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

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

第五迭代。现在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];
}