【问题标题】:C++ compilation bug?C++编译错误?
【发布时间】:2015-12-07 00:31:27
【问题描述】:

我有以下代码:

#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&lt;4 效果不好,总是返回 true。

如果我只是注释掉,delta=mc[di],我会正常得到“0、1、2、3”。无辜的分配有什么问题?

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

【问题讨论】:

  • 我已经用 g++ 试过了,没有优化它工作正常。使用 -O3 它给出了 OP 提到的行为。使用 -O1 就可以了。
  • 由于this case等未定义行为,听起来像是激进的循环优化
  • 虽然代码确实调用了未定义的行为,但优化相当激进,据我观察,gcc 无法始终为此优化提供警告并没有帮助。
  • @Shafik Yaghmour GCC 不提供cout &lt;&lt; di 警告的原因可能是复杂的流插入运算符将di 的地址传递给一些“不透明”代码(或complex 的流插入运算符本身是不透明的——尽管这会让我感到惊讶)。并且根据“不透明”代码的作用,程序的行为仍然可以很好地定义。我并不是说如果没有太多误报(甚至任何误报),在这种情况下就不可能提供警告。只是这会很困难。
  • @ShafikYaghmour 我正在使用带有 -O2 选项的 Ideone.com g++ C++14。 (谢谢,我在我的问题中添加了它。)

标签: c++ gcc undefined-behavior


【解决方案1】:

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

  • 越界访问mc 是未定义的行为
  • 假设没有未定义的行为
  • 因此di &lt; 4 始终为真,否则mc[di] 将调用未定义的行为

gcc 打开优化并使用-fno-aggressive-loop-optimizations 标志会导致无限循环行为消失(see it live)。而live example with optimization but without -fno-aggressive-loop-optimizations 表现出您观察到的无限循环行为。

godbolt live example of the code 显示 di &lt; 4 检查已删除并替换为无条件 jmp:

jmp .L6

这与GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks 中概述的情况几乎相同。本文的 cmets 非常出色,值得一读。它指出,clang 在文章中使用 -fsanitize=undefined 捕获了该案例,我无法重现此案例,但使用 -fsanitize=undefined 的 gcc 可以(see it live)。围绕未定义行为进行推断的优化器最臭名昭著的错误可能是Linux kernel null pointer check removal

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

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

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

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

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

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

这可能足以为 OP 提供足够的信息来弄清楚发生了什么。像这样的不一致是我们可以看到的未定义行为的典型行为类型。为了更好地理解为什么这种警告在面对未定义的行为时会不一致,Why can't you warn when optimizing based on undefined behavior? 是一本很好的读物。

注意,-fno-aggressive-loop-optimizations 记录在 gcc 4.8 release notes 中。

【讨论】:

  • 这个。未定义的行为不是(仅)与崩溃有关。这是关于假设的,如果你违反了编译器的假设(由语言规范定义),那么所有的赌注都没有......
  • @MatthieuM。事实上,我们不断回到这个主题,quotes in my answer here 中的一些也是相关的。
  • 我真是个傻瓜,我没有注意到我正在访问 mc[4] ;) 我使用的是 Ideone.com,所以无论如何我都不会收到警告。下次我将使用即使编译成功也会给我警告的编辑器:)
  • @eivour 提供指向您的在线示例的链接会很有帮助,在这种情况下,我认为每次您在 ideone 中运行示例时,它都会提供该示例的 url。我个人更喜欢使用ColiruWandbox,它们都提供了分享按钮。
  • gcc 4.9.3 仍然没有产生任何警告:g++ -Waggressive-loop-optimizations -Wall -Wextra -O3 test.cpp - 根据此页面,编译器应显示警告:gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
【解决方案2】:

由于您在使用 di 索引 mc 之前递增 di,因此第四次循环您将引用 mc[4],它超出了数组的末尾,这反过来可能导致麻烦的行为。

【讨论】:

  • 忽略我的最后一条评论,这可能是 ideone.com 在编辑后无法正确重新运行我的代码的问题。另一个测试确实有效:ideone.com/vLtvcy
  • 使用di++,delta=mc[di-1]delta=mc[di],di++ 也可以解决问题。看起来 Logicrat 是正确的。
  • 也许有一个错误,并且 eivour 是指 delta=mc[di++] 而不是 delta=mc[++di],以使用所有 mc 值?
【解决方案3】:

你有这个:

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 循环中执行代码块。

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

第三次迭代:现在 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 现在有垃圾,这是未定义的行为或损坏。最后使用“garbage delta”在for循环内执行代码块。

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

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

【讨论】:

    【解决方案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];
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-01
      • 2011-05-16
      相关资源
      最近更新 更多