2012-06-06 110 views
-2

我看到了,我想了解一個令人費解的行爲......瞭解gcc的優化

示例代碼.. 請忽略這一事實,我返回一個局部變量的地址..

編輯:我只是用這個代碼作爲例子來理解gcc的優化行爲。我不認爲這個示例代碼中的未定義行爲改變了gcc的優化邏輯。如果你認爲它,請解釋。

#include<stdio.h> 
    char *foo() { 
     char arr[] = "hello world is here..\n"; 
     return arr; 
    } 

    int main() { 
     char *ptr; 

     ptr = foo(); 
     printf("0x%x \n", ptr); 
     printf("%s", ptr); 
    } 

在Linux/x86機器上運行這一點,在主第一個printf()不會打印地址..但第二次的printf不打印任何東西。它看起來像gcc以某種方式優化了數組初始化。

如果我像下面那樣更改foo(),那麼字符串會被正確打印..我知道它是一個未定義的行爲。但我只對這裏的gcc優化有興趣。

char *foo() { 
     char arr[] = "hello\n"; 
     printf("0x%x\n", arr); 
     return arr; 
    } 

在原始代碼中,foo怎麼可能返回一個地址,但是初始化被優化了?這是彙編代碼..我不熟悉x86彙編......在這兩種情況下,gcc究竟做了什麼?

  .LC0: 
        .string "hello\n" 
        .text 
      .globl foo 
        .type foo, @function 
      foo: 
      .LFB2: 
        pushq %rbp 
      .LCFI0: 
        movq %rsp, %rbp 
      .LCFI1: 
        movl .LC0(%rip), %eax 
        movl %eax, -16(%rbp) 
        movzwl .LC0+4(%rip), %eax 
        movw %ax, -12(%rbp) 
        movzbl .LC0+6(%rip), %eax 
        movb %al, -10(%rbp) 
        leaq -16(%rbp), %rax 
        leave 
        ret 
      .LFE2: 

和Foo()與額外的printf的彙編代碼..

  .LC0: 
        .string "hello\n" 
      .LC1: 
        .string "0x%x\n" 
        .text 
      .globl foo 
        .type foo, @function 
      foo: 
      .LFB2: 
        pushq %rbp 
      .LCFI0: 
        movq %rsp, %rbp 
      .LCFI1: 
        subq $16, %rsp 
      .LCFI2: 
        movl .LC0(%rip), %eax 
        movl %eax, -16(%rbp) 
        movzwl .LC0+4(%rip), %eax 
        movw %ax, -12(%rbp) 
        movzbl .LC0+6(%rip), %eax 
        movb %al, -10(%rbp) 
        leaq -16(%rbp), %rsi 
        movl $.LC1, %edi 
        movl $0, %eax 
        call printf 
        leaq -16(%rbp), %rax 
        leave 
        ret 
+6

你的問題聽起來像'請忽略我假定獨角獸退出的事實,但玉米的目的是什麼?' – UmNyobe

+1

事實上,它不打印任何東西只是意味着你是幸運的;這可能意味着'ptr [0] =='\ 0''。 –

+1

您正在用'%x'打印一個指針。如果你的指針和'unsigned'具有不同的寬度,你又有未定義的行爲。 '%p'是'void *'指針的正確格式。 –

回答

6

Please ignore that fact that I am returning a local variable address..

以及你也在使用該函數的返回值,我們不能忽視這個。 C說你的程序調用未定義的行爲,實現有權做任何想做的事情。

1

「未定義的行爲」意味着編譯器維護者不必擔心這種情況。也就是說,編譯器在這種情況下所做的是完全沒有意義的。沒有什麼可以「理解」,除非你不應該調用未定義的行爲,有史以來

如果您希望任何人花時間處理您的問題,請創建一個明確定義的示例。

3

發生了什麼很容易解釋:你已經告訴編譯器把一些數據放在堆棧上,返回這個地址並且再次釋放它。所以你有地址,但你沒有關於內容的證據。

從函數返回後,您需要撥打printf()兩次。首先輸出給定地址,然後輸出它包含的內容。

在到達輸出點之前,堆棧內容可能(顯然會)與其他東西混淆。我可以想象printf()內部需要一定數量的堆棧並使用它(=寫入它),破壞了原始內容。

儘管其他人完全正確地聲明所述行爲是未定義的,但並不妨礙爲編譯器行爲表現的方式行事。


編輯:在這種情況下,這是不夠的,看彙編代碼FPR foo()。優化還意味着函數調用可以被函數包含的代碼替換。自己編寫代碼之後,似乎在這裏就是這種情況。 foo的代碼仍然包含在目標文件中,但也在main()中內聯。這使得所述數組本地到main(),並且保持分配直到結束。但如前所述,這只是巧合,沒有什麼可以依賴的。

彙編代碼看起來像這樣:

.LC1: 
    .string "0x%x \n" 
.LC2: 
    .string "%s" 
    .text 
    .p2align 4,,15 
.globl main 
    .type main, @function 
main: 
    pushl %ebp 
    movl %esp, %ebp 
    andl $-16, %esp 
    pushl %ebx 
    subl $60, %esp 
    leal 26(%esp), %ebx 
    movl %ebx, 4(%esp) 
    movl $1819043176, 26(%esp) 
    movl $1952784495, 30(%esp) 
    movl $1696607843, 34(%esp) 
    movl $539911028, 38(%esp) 
    movl $778269797, 42(%esp) 
    movw $10, 46(%esp) 
    movl $.LC0, (%esp) 
    call printf 
    movl %ebx, 4(%esp) 
    movl $.LC1, (%esp) 
    call printf 
    movl %ebx, 4(%esp) 
    movl $.LC2, (%esp) 
    call printf 
    addl $60, %esp 
    popl %ebx 
    movl %ebp, %esp 
    popl %ebp 
    ret 

這裏你可以看到 - 有沒有callfoo();數據以4字節的塊寫入堆棧,並立即發送給printf

+0

+1。我認爲最後一段是誤解了這個問題。什麼工作(並且必須工作)是'foo'中的'printf',其中數組仍然存在。 –

+0

@undur_gongor你是完全正確的。不僅如此:僅僅是一個'printf(「0x%x \ n」,arr);'在任何情況下都會輸出一個值,如果它是一個有效的地址或不是。 – glglgl

+0

這是正確的 - 很可能第一個printf()調用正在清除棧中存儲的foo()的字符串。 – caf

0

您如何檢查給定代碼中的優化?
return arr將返回數組的基地址。當此地址轉到main時,arr指向的內存被破壞。在發生這種情況之後,您可能會遇到段錯誤,您的程序可能會崩潰。使用未初始化的指針時發生的任何事情都可能發生。這段代碼沒有檢查優化。它可以幫助你理解你已經知道在你的文章中寫的一個未定義的行爲!