不同的优化级别可以导致功能不同的代码吗?

时间:2011-06-15 21:05:05

标签: c++ c gcc compiler-optimization

我很好奇编译器在优化时的自由度。让我们将这个问题限制在GCC和C / C ++(任何版本,任何标准版本):

是否可以编写行为的代码,具体取决于编译它的优化级别?

我想到的例子是在C ++中的各种构造函数中打印不同的文本位,并根据副本是否被省略而获得差异(尽管我无法使这样的东西工作)。

不允许计算时钟周期。如果你有一个非GCC编译器的例子,我也很好奇,但我无法检查它。 C中示例的加分点: - )

编辑:示例代码应符合标准,并且从一开始就不包含未定义的行为。

编辑2:已经有了一些很棒的答案!让我稍微了解一下:代码必须构成一个格式良好的程序并且符合标准,并且必须在每个优化级别编译为正确的,确定性的程序。 (这不包括形状错误的多线程代码中的竞争条件等。)我也理解浮点舍入可能会受到影响,但让我们对此进行折扣。

我只获得了800点声望,所以我想我将在第一个完整的例子上赢得50点声望,以符合这些条件的(精神); 25如果涉及滥用严格别名。 (视某人向我展示如何向他人发送赏金。)

13 个答案:

答案 0 :(得分:19)

适用的C ++标准部分是§1.9“程序执行”。它的部分内容如下:

  

符合实现需要模拟(仅)抽象机器的可观察行为,如下所述。 ...

     

执行格式良好的程序的一致实现应该产生与具有相同程序和相同输入的抽象机的相应实例的可能执行序列之一相同的可观察行为。 ...

     

抽象机器的可观察行为是它对易失性数据的读写顺序以及对库I / O函数的调用。 ...

所以,是的,代码可能在不同的优化级别上表现,但是(假设所有级别都产生一致的编译器),但它们不能以不同的方式表现 。 / p>

编辑:请允许我更正我的结论:是的,只要每个行为与标准抽象机器的某个行为明显相同,代码在不同的优化级别上的行为可能会有所不同。

答案 1 :(得分:15)

  

是否可以编写代码   表现不同取决于哪个   它编译的优化级别   与?

仅当您触发编译器的错误。

修改

此示例在gcc 4.5.2上的行为有所不同:

void foo(int i) {
  foo(i+1);
}

main() {
  foo(0);
}

使用-O0编译会导致程序崩溃并出现分段错误 使用-O2编译创建了一个程序进入无限循环。

答案 2 :(得分:13)

浮点计算是差异的成熟来源。根据各个操作的排序方式,您可以获得更多/更少的舍入错误。

根据内存访问的优化方式,不太安全的多线程代码也可能有不同的结果,但无论如何,这基本上都是代码中的错误。

正如您所提到的,当优化级别发生变化时,复制构造函数中的副作用可能会消失。

答案 3 :(得分:10)

好的,通过提供一个具体的例子,我公然为赏金做游戏。我会把其他人的答案和我的评论放在一起。

出于不同优化级别的不同行为的目的,“优化级别A”应表示gcc -O0(我使用的是版本4.3.4,但它并不重要,我认为任何甚至是模糊的近期版本将显示我之后的差异),“优化级别B”将表示gcc -O0 -fno-elide-constructors

代码很简单:

#include <iostream>

struct Foo {
    ~Foo() { std::cout << "~Foo\n"; }
};

int main() {
    Foo f = Foo();
}

优化级别A的输出:

~Foo

优化级别B的输出:

~Foo
~Foo

代码是完全合法的,但由于复制构造函数elision,输出依赖于实现,特别是它对禁用复制ctor elision的gcc优化标志敏感。

请注意,一般来说,“优化”是指编译器转换,它可以更改未定义,未指定或实现定义的行为,但不会更改标准定义的行为。因此,满足您的标准的任何示例都必须是一个程序,其输出未指定或实现定义。在这种情况下,标准是否未指明复制ctors是否被省略,我恰巧幸运的是GCC在允许的情况下可靠地完全忽略了它们,但是可以选择禁用它。

答案 4 :(得分:8)

对于C,几乎所有操作都在抽象机器中严格定义,并且只有在可观察结果与该抽象机器完全相同时才允许进行优化。想到这条规则的例外情况:

  • 不必定义未定义的行为 不同编译器之间一致 运行或执行错误代码
  • 浮点运算可能会导致 不同的舍入
  • 函数调用的参数可以是 按任何顺序评估
  • volatile限定的表达式 类型可能会也可能不会被评估 为了他们的副作用
  • 相同的const合格的复合文字可能会或可能不会折叠到一个静态内存位置

答案 5 :(得分:4)

根据标准,任何未定义行为都可以根据优化级别(或月相,相应)改变其行为。

答案 6 :(得分:2)

如果您有两个指向同一内存块的指针,-fstrict-aliasing选项很容易导致行为更改。这应该是无效的,但实际上很常见。

答案 7 :(得分:2)

由于复制构造函数调用可以被优化掉,即使它们有副作用,具有副作用的复制构造函数也会导致未经优化和优化的代码表现不同。

答案 8 :(得分:1)

此C程序调用未定义的行为,但在不同的优化级别中显示不同的结果:

#include <stdio.h>
/*
$ for i in 0 1 2 3 4 
    do echo -n "$i: " && gcc -O$i x.c && ./a.out 
  done
0: 5
1: 5
2: 5
3: -1
4: -1
*/

void f(int a) {
  int b;
  printf("%d\n", (int)(&a-&b));
}
int main() {
 f(0);
 return 0;
}

答案 9 :(得分:0)

相同的源代码,例如 source code

在启用-finline-small-functions之前和之后在启用-finline-small-functions之后

Before enable -finline-small-functions

After enable -finline-small-functions

可以在-O2 / -O3中启用

-finline-small-functions

答案 10 :(得分:0)

两个不同的C程序:

foo6.c

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Message Chart</title>
    <!-- import plugin script -->
    <script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js'></script>
  </head>
  <body>
    <h1> Messages Per Year </h1>
    <!-- bar chart canvas element -->
    <canvas id="myChart" width="600" height="400"></canvas>
    <p id="caption">The chart is displaying a line chart of messages per year.</p>

    <script>
      // Global parameters:
      // do not resize the chart canvas when its container does (keep at 600x400px)
      Chart.defaults.global.responsive = false;
      hoverRadius: 4;
      // define the chart data
      var chartData = {
        labels : [{% for item in labels %}
                   "{{item}}",
                  {% endfor %}],
        datasets : [{
            label: '{{ legend }}',
            fill: false,
            lineTension: 0.1,
            backgroundColor: "rgba(75,192,192,0.4)",
            borderColor: "rgba(75,192,192,1)",
            borderCapStyle: 'butt',
            borderDash: [],
            borderDashOffset: 0.0,
            borderJoinStyle: 'miter',
            pointBorderColor: "rgba(75,192,192,1)",
            pointBackgroundColor: "#fff",
            pointBorderWidth: 1,
            pointHoverRadius: 8,
            pointHoverBackgroundColor: "rgba(75,192,192,1)",
            pointHoverBorderColor: "rgba(220,220,220,1)",
            pointHoverBorderWidth: 2,
            pointRadius: 1,
            pointHitRadius: 10,
            data : [{% for item in values %}
                     {{item}},
                    {% endfor %}],
            spanGaps: false
        }]
      }

      // get chart canvas
      var ctx = document.getElementById("myChart").getContext("2d");

      // create the chart using the chart canvas
      var myChart = new Chart(ctx, {
        type: 'line',
        data: chartData,
      });
    </script>
  </body>
</html>

bar6.c

void p2(void);

int main() {
    p2();
    return 0;
}

将两个模块编译为一个可执行文件时 优化级别1和0,它们会打印出两个不同的值。 -O1为0x48,-O0为0x55

Screenshot of terminal

这是它在我的环境中工作的一个示例

答案 11 :(得分:0)

a.c:

char *f1(void) { return "hello"; }

b.c:

#include <stdio.h>

char *f1(void);

int main()
{
    if (f1() == "hello") printf("yes\n");
        else printf("no\n");
}

输出取决于是否启用合并字符串常量优化:

  

$ gcc a.c b.c -o a -fno-merge-constants; ./a
  否
  $ gcc a.c b.c -o a -fmerge-constants; ./a
  是的

答案 12 :(得分:-1)

今天在我的操作系统课程中得到了一些有趣的例子。 我们分析了一些可能在优化时损坏的软件互斥体,因为编译器不知道并行执行。

编译器可以重新排序不依赖于数据的语句。 由于我已经在并行化代码中使用了这个依赖关系,因此它可能会破坏编译器。 我给出的示例会导致调试困难,因为线程安全性被破坏,并且由于操作系统调度问题和并发访问错误,您的代码行为不可预测。