2011-09-02 54 views
9
#include <stdio.h> 

char toUpper(char); 

int main(void) 
{ 
    char ch, ch2; 
    printf("lowercase input : "); 
    ch = getchar(); 
    ch2 = toUpper(ch); 
    printf("%c ==> %c\n", ch, ch2); 

    return 0; 
} 

char toUpper(char c) 
{ 
    if(c>='a'&&c<='z') 
     c = c - 32; 
} 

在TOUPPER函數,返回類型是char,但在TOUPPER沒有 「返回」()。並用gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4),fedora-14編譯源代碼。GCC爲什麼以及如何編譯一個缺少return語句的函數?

當然,發出警告:「警告:控制到達非void函數結束」,但是,運作良好。

使用gcc進行編譯時,代碼中發生了什麼? 我想在這種情況下得到一個堅實的答案。 謝謝:)

+5

聞起來像未定義行爲。 – ThiefMaster

+2

@ThiefMaster:它**是** UB。他很幸運,通常放置返回值的寄存器恰好也用於減法。 –

+0

謝謝大家:) – harrison

回答

19

發生了什麼事對你來說,當C程序被編譯成彙編語言,你toupper函數會弄成這個樣子,也許是:

_toUpper: 
LFB4: 
     pushq %rbp 
LCFI3: 
     movq %rsp, %rbp 
LCFI4: 
     movb %dil, -4(%rbp) 
     cmpb $96, -4(%rbp) 
     jle  L8 
     cmpb $122, -4(%rbp) 
     jg  L8 
     movzbl -4(%rbp), %eax 
     subl $32, %eax 
     movb %al, -4(%rbp) 
L8: 
     leave 
     ret 

的32減法是在%EAX寄存器進行。在x86調用約定中,這是返回值預期爲的寄存器!所以......你很幸運。

但請講究警告。他們在那裏是有原因的!

+0

+1顯示asm –

+1

這正是我所猜測的:使用eax,包含操作的結果,eax是返回值。但是,男孩,當你習慣了英特爾風格時,很難閱讀GNU風格的彙編。 –

+0

現在我知道這個問題是什麼。我看到了警告,但我只是想知道。謝謝:) – harrison

7

它取決於Application Binary Interface和哪些寄存器用於計算。

E.g.在x86上,第一個函數參數和返回值存儲在EAX中,所以gcc最有可能使用它來存儲計算結果。

+0

我讀了ABI和調用約定。謝謝:) – harrison

1

我不能告訴你,你的平臺的細節,因爲我不知道,但有一種普遍的答案,你看到的行爲。

當編譯了一個具有返回的函數時,編譯器將使用關於如何返回該數據的約定。它可能是一個機器寄存器,或者是一個定義的存儲器位置,例如通過堆棧或其他(儘管通常使用機器寄存器)。編譯後的代碼也可以在執行該功能時使用該位置(註冊或以其他方式)。

如果函數不返回任何內容,那麼編譯器不會生成代碼,明確填充帶有返回值的位置。不過就像我上面所說的那樣,它可能會在該功能中使用該位置。當您編寫讀取返回值(ch2 = toUpper(ch);)的代碼時,編譯器將編寫使用其慣例的代碼,以檢索從傳統位置返回的返回值。就調用者代碼而言,即使沒有明確寫入,它也會從該位置讀取該值。因此你會得到一個價值。

現在看看@ Ray的示例,編譯器使用的EAX寄存器,存儲所述上殼體的操作的結果。它恰好如此,這可能是返回值寫入的位置。在調用端,ch2加載了EAX中的值 - 因此是幻像返回。這隻適用於x86系列處理器,因爲在其他架構上,編譯器可以使用完全不同的方案來決定應該如何組織約定

然而好的編譯器會嘗試根據本地條件集,知識代碼,規則和啓發式。所以重要的一點是,這只是運氣而已。編譯器可以優化而不是做這個或者什麼 - 你不應該回應這個行爲。

+0

-1聽起來像是一箇中國的幸運餅乾...更具體的 – Quamis

+0

你究竟想讓我更具體一些嗎? –

+0

retracted my -1 :) – Quamis

2

本質上,c被推入到應該稍後用返回值填充的位置;因爲它沒有被return的使用覆蓋,所以它返回的值結束。

請注意,依賴於此(使用C語言或任何其他語言(這不是Perl的顯式語言功能))是一個糟糕的想法™。在極端。

+0

答案是%eax寄存器。我知道了 – harrison

2

一個很重要的理解是缺少一個return語句的診斷錯誤。考慮一下這個功能:

int f(int x) 
{ 
    if (x!=42) return x*x; 
} 

只要你從來沒有與42的參數調用它,包含此功能的程序是完全合法的C,不調用任何不確定的行爲,儘管它調用如果您撥打f(42)並隨後嘗試使用返回值,則爲UB。因此,雖然編譯器可能會提供缺少返回語句的警告啓發式,但如果沒有誤報或漏報,就不可能這樣做。這是不可能解決暫停問題的結果。

+2

+1很高興澄清這一點。 Java和Ada的關於缺少返回值的編譯時錯誤只要檢測到存在_exists_通過函數的某個路徑而不會返回語句就可以退出,而不是確保這樣的路徑。 –

0

沒有局部變量,所以函數結尾的堆棧頂部的值將是參數c。退出時堆棧頂部的值是返回值。所以無論C持有多少,這就是回報價值。

+0

謝謝你簡單的指示 – harrison

0

您應該記住,這樣的代碼可能會因編譯器而崩潰。例如,clang會在這樣的函數結束時生成ud2指令,並且您的應用程序將在運行時崩潰。

0

我已經嘗試了小PROGRAMM:

#include <stdio.h> 
int f1() { 
} 
int main() { 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
} 

結果:

TEST:< 1>

TEST:< 10>

TEST:< 11>

TEST:< 11>

TEST:< 11>

我用的mingw32-gcc編譯器,所以有可能是個體差異。

你可以玩耍並嘗試一個char函數。 只要你不使用結果值,它將仍然工作正常。

#include <stdio.h> 
char f1() { 
} 
int main() { 
    f1(); 
} 

但我stil會建議設置void函數或給一些返回值。

你的功能似乎需要一個回報:

char toUpper(char c) 
{ 
    if(c>='a'&&c<='z') 
     c = c - 32; 
    return c; 
}