繁体   English   中英

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

[英]compile with GCC -O2 option generate different program

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

对于gcc 4.4.4,这与-O0-O2不同

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

main() {
  foo(0);
}

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

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

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

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) )以及while条件的评估( cmpl $0, -4(%ebp) )和main函数的返回值( movl $0, %eax )。

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

优化正在使用关于的假设

  • 在某些情况下没有指针别名(意味着它可以将东西保存在寄存器中而不用担心通过另一个引用进行修改)
  • 一般来说,内存位置不会波动

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

 Type-punned pointers may break strict aliasing rules... (paraphrased)

这些警告旨在帮助您避免在编译智能和优化时代码产生细微错误时头痛。

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

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

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

输入我的HTC。 原谅一两个错字

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

#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

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

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

很少发现-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也会产生不同的结果。 像上面那些简单的函数通常会产生与许多编译器优化相同的结果。 但是,具有更多变量的更复杂的函数可能会产生从编译器到编译器的不同结果。

下面的代码输出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;
}

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM