用GCC编译-O2选项生成不同的程序

时间:2011-03-24 17:10:40

标签: c optimization compiler-construction

我听说带有/不带优化选项的C编译器可能会生成不同的程序(用优化编译程序会导致它的行为不同),但我从未遇到过这样的情况。 任何人都可以给出一个简单的例子来表明这一点吗?

9 个答案:

答案 0 :(得分:5)

对于gcc 4.4.4,这与-O0-O2

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

main() {
  foo(0);
}

通过优化,这将永远循环。没有优化,它崩溃(堆栈溢出!)

其他更现实的变体通常取决于时序,易受浮点精确度变化的影响,或取决于未定义的行为(未初始化的变量,堆/堆栈布局)

答案 1 :(得分:3)

如果查看此代码生成的程序集:

int main ()
{
    int i = 1;
    while (i) ;
    return 0;
}

Whitout -O2标志:

 .file   "test.c"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    $1, -4(%ebp)
.L2:
    cmpl    $0, -4(%ebp)
    jne .L2
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

使用-O2标志:

 .file   "test.c"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
.L2:
    jmp .L2
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

使用-O2标志,i的声明和返回值被省略,并且您只有一个标签在同一标签上跳转以构成无限循环。

如果没有-O2标志,您可以清楚地看到堆栈上i空间的分配(subl $16, %esp)和初始化(movl $1, -4(%ebp))以及对时间的评估condition(cmpl $0, -4(%ebp))和main函数的返回值(movl $0, %eax)。

答案 2 :(得分:2)

我在程序中看到它在浮点精度限制附近做了很多数学运算。 在极限情况下,算术不是关联的,因此如果以稍微不同的顺序执行操作,则可以获得稍微不同的答案。此外,如果使用具有80位双精度的浮点芯片,但结果存储在64位双精度变量中,则信息可能会丢失,因此操作顺序会影响结果。

答案 3 :(得分:2)

优化正在使用关于

的假设
  • 在某些情况下没有指针别名(意味着它可以将内容保存在寄存器中,而不必担心通过另一个引用进行修改)
  • 一般情况下内存位置的非易失性

也正是因为这样你才能得到像

这样的警告
 Type-punned pointers may break strict aliasing rules... (paraphrased)

这些警告旨在帮助您避免头痛,当您的代码在编译机智时会出现微妙的错误。优化。

一般来说,在c和C ++中

  • 非常确定你知道自己在做什么
  • 从不松散地播放(不要将char **直接转换为char *等)
  • 使用const,volatile,throw(),尽职尽责
  • 信任您的编译器供应商(或开发人员)或构建-O0

我确信我错过了史诗,但你得到漂移。

输入我的htc。请原谅一两个错误

答案 4 :(得分:1)

优化级别之间的差异通常源于未初始化的变量。例如:

#include <stdio.h>

int main()
{
    int x;
    printf("%d\n", x);
    return 0;
}

使用-O0进行编译时,输出5895648。使用-O2编译时,每次运行时输出不同的数字;例如,-1077877612

差异可能更微妙;想象你有以下代码:

int x; // uninitialized
if (x % 10 == 8)
    printf("Go east\n");
else
    printf("Go west\n");

使用-O0,这将输出Go east,并-O2,(通常)Go west

答案 5 :(得分:1)

在错误提交报告中可以找到在不同优化级别上具有不同输出的正确程序的示例,并且它们仅在特定版本的GCC上“起作用”。

但是通过调用UB很容易实现它。但是,它不再是一个正确的程序,并且还可以使用不同版本的GCC生成不同的输出(除其他外,请参阅mythology)。

答案 6 :(得分:0)

很少发现-O2不会产生与不使用优化不同的结果的情况。

unsigned int fun ( unsigned int a )
{
   return(a+73);
}

没有优化:

fun:
    str fp, [sp, #-4]!
    .save {fp}
    .setfp fp, sp, #0
    add fp, sp, #0
    .pad #12
    sub sp, sp, #12
    str r0, [fp, #-8]
    ldr r3, [fp, #-8]
    add r3, r3, #73
    mov r0, r3
    add sp, fp, #0
    ldmfd   sp!, {fp}
    bx  lr

优化:

fun:
    add r0, r0, #73
    bx  lr

即使是这个功能:

void fun ( void )
{
}

没有优化:

fun:
    str fp, [sp, #-4]!
    .save {fp}
    .setfp fp, sp, #0
    add fp, sp, #0
    add sp, fp, #0
    ldmfd   sp!, {fp}
    bx  lr

通过优化:

fun:
    bx  lr

如果你声明一切都是易变的并且需要帧指针,你可能会接近未经优化和优化的东西。同样,如果你编译了一个可调试版本(不确定那个开关是什么),那就好像一切都是易失性的,这样你就可以使用调试器来监视内存中的变量并单步执行。也可能从同一输入接近相同的输出。

另请注意,无论是否进行优化,都会看到来自不同编译器的相同源代码的不同输出,即使gcc的不同主要版本产生不同的结果。像上面那些简单的函数通常会产生与许多编译器优化相同的结果。但是,具有更多变量的更复杂的函数可能会产生从编译器到编译器的不同结果。

答案 7 :(得分:0)

以下代码在没有优化的情况下编译时输出Here i am,但在使用优化进行编译时没有输出。

这个想法是函数x()被指定为“纯”(没有副作用),所以编译器可以优化它(我的编译器是 gcc 4.1.2

#include <stdio.h>

int x() __attribute__ ((pure));

int x()
{
    return printf("Here i am!\n");
}

int main()
{
    int y = x();
    return 0;
}

答案 8 :(得分:-1)

这个问题的一个答案可能是:

每个ANSI C编译器至少需要支持:

  • 函数定义中的31个参数
  • 函数调用中的31个参数
  • 源行中的509个字符
  • 表达式中的32个嵌套括号
  • long int的最大值不能小于2,147,483,647,(即长整数 至少32位)。

来源:专家C编程 - Peter van den Linden

可能是编译器在-O0的函数定义中支持31个参数,在-O3的函数定义中支持35个,这是因为没有规范。我个人认为这应该是一个缺陷设计,非常可以改进。但简而言之:编译器中的某些东西不受标准限制,可以改变包括优化级别在内的实现。

希望这有助于像Mark Loeser所说的那样,你的问题应该更具体。