【问题标题】:Different evaluation when compiling with -O3使用 -O3 编译时的不同评估
【发布时间】:2020-01-13 07:01:39
【问题描述】:

以下代码在使用g++ -O3 .. 和不使用-O3 编译时具有不同的行为。

我知道updateError 没有返回任何内容,这可能导致未定义的行为。我不明白的是,如果使用 -O3 编译,error 在循环内设置为零,if(error > eps) 被评估为 false,而在 while() 语句中仍被评估为 true。 .

我还尝试将其更改为 do{}while() 循环,在这种情况下它也适用于 -O3...

#include <stdio.h>


using namespace std;


double updateError(){
    printf("updating Error\n");
}

int main(int argc, char *argv[]){

    double eps = 1.e-10;
    double error = 2*eps;
    int iter = 0;

    while(error > eps){
        error = updateError();
        iter++;
        printf("Error on iteration %i: %.20e\n", iter, error);
        if(error > eps) printf("error bigger\n\n");
        else printf("error smaller\n\n");
        if(iter == 5){
            printf("not converged in 5 iterations!!\n");
            break;
        }
    }
    return 0;
}

使用 -O3 的控制台输出:

updating Error
Error on iteration 1: 0.00000000000000000000e+00
error smaller

updating Error
Error on iteration 2: 0.00000000000000000000e+00
error smaller

updating Error
Error on iteration 3: 0.00000000000000000000e+00
error smaller

updating Error
Error on iteration 4: 0.00000000000000000000e+00
error smaller

updating Error
Error on iteration 5: 0.00000000000000000000e+00
error smaller

not converged in 5 iterations!!

没有 -O3 的控制台输出:

updating Error
Error on iteration 1: 6.92743341769227318242e-310
error smaller

【问题讨论】:

  • 这不是“它会导致未定义的行为”。 这是未定义的行为。允许编译器对代码执行任何操作,尤其是在优化代码时。
  • 我知道 updateError 没有返回任何东西,这可能导致未定义的行为不能,但 。在包含 UB 的情况下分析您的代码是没有意义的。由于执行路径遇到了UB,你的整个程序就变成了UB,任何事情都可能随时发生。
  • 您可能想要检查生成的程序集并将优化的构建与未优化的构建进行比较。一个很好的方法是通过compiler explorer site。可以看出,可能会有很大的不同!
  • 在我的系统上,输出是runtime error: execution reached the end of a value-returning function without returning a value
  • 您明确地编写了一个未定义的行为,并提出了一个问题,即为什么程序的行为不像您所期望的那样。 UB 意味着你不能期待任何事情(或者换句话说,任何事情都可能发生)。这个问题听起来像“我有一个玩具,我用锤子把它弄坏了。你能解释一下为什么它现在坏了吗?” :)

标签: c++ gcc optimization while-loop evaluation


【解决方案1】:

我知道 updateError 没有返回任何内容,这可能导致未定义的行为。

没有“可以”。您的代码具有未定义的行为。期间。

在调试构建时,编译器会做一些额外的事情来增强调试器的使用。这可能是初始化变量时实际上不必这样做的事情。这种开销不会在优化的构建中完成。无论如何,如果你想了解为什么你会得到你得到的结果,你应该看看这个程序集。请注意,UB 就是 UB,因此您无法保证在第二天使用相同的编译器获得相同的结果。

我不明白的是,如果使用 -O3 编译,则该错误在循环内设置为零,并且 if(error > eps) 被评估为 false,而在 while() 中仍被评估为 true陈述。 .

请注意,您编写的代码不会逐行转换为 CPU 的指令。编译器相当聪明,可以在 as-if 规则下应用各种转换。只要可观察到的行为相同,编译器就可以将您的代码转换为与简单的逐行翻译完全不同的东西。但是,如果您的代码有 UB,那么它不是有效的 c++ 代码,并且为一个相同的变量获得不同的输出也就不足为奇了。

【讨论】:

  • 附带说明,这是 明显 UB。有时,它不太明显。在这种情况下,我建议在 Valgrind 下运行二进制文件,它会报告 Conditional move depends on uninitialized value 错误,必须修复。
  • 编译器是确定性的。因此,第二天,您将使用相同的编译器得到相同的结果。您可能的意思是,在再次运行时您可能不会得到相同的程序输出,因为程序当前正在选择返回 updateError 的任何寄存器状态,并且由于加载时程序在内存中的随机放置,它可以是不同。
  • @xryl669 这有点草率。也许您会得到相同的结果,但我非常怀疑编译器会对此做出任何保证
  • 我明白了,那么有什么理由允许在非 void 函数中不返回变量?
  • @atsteich 重点是:这是不允许的。问题只是一般来说编译器不能轻易决定是否是这种情况。在您的简单示例中,您应该收到警告,但请注意,有很多事情是不允许的,但编译器不需要发出错误
猜你喜欢
  • 2011-09-27
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-21
  • 2012-01-14
相关资源
最近更新 更多