簡體   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