簡體   English   中英

GCC為什么以及如何通過缺少return語句來編譯函數?

[英]Why and how does GCC compile a function with a missing return statement?

#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()中沒有“ return”。 並使用gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4),fedora-14編譯源代碼。

當然,會發出警告:“警告:控制到達非無效功能的盡頭”,但是效果很好。

用gcc編譯期間該代碼中發生了什么? 在這種情況下,我想得到一個可靠的答案。 謝謝 :)

您遇到的是,當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

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

但是請注意警告。 他們在那里是有原因的!

它取決於應用程序二進制接口以及用於計算的寄存器。

例如在x86上,第一個函數參數和返回值存儲在EAX ,因此gcc最有可能也使用它來存儲計算結果。

本質上,將c推送到應該稍后用返回值填充的位置; 由於不會被return覆蓋,因此最終會返回為值。

請注意,依賴於此(使用C語言或其他不是顯式語言功能的語言,例如Perl)是Bad Idea™。 在極端。

需要了解的一件事很重要,那就是省略return語句很少是可診斷的錯誤。 考慮以下功能:

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

只要你永遠不與42的參數調用它,包含此功能的程序是完全合法的C和,不調用任何不確定的行為,盡管事實上,它調用UB如果你叫f(42)並隨后試圖使用返回值。

這樣,盡管編譯器有可能為缺少的return語句提供警告啟發式,但如果沒有誤報或誤報,就不可能這樣做。 這是不可能解決暫停問題的結果。

我不知道您平台的細節,因為我不知道,但是您所看到的行為有一個普遍的答案。

編譯具有返回值的某個函數時,編譯器將使用有關如何返回該數據的約定。 它可以是機器寄存器,也可以是定義的內存位置,例如通過堆棧或其他任何方式(盡管通常使用機器寄存器)。 編譯的代碼在執行功能時也可以使用該位置(注冊或其他方式)。

如果函數不返回任何內容,則編譯器將不會生成用返回值顯式填充該位置的代碼。 但是,就像我上面說的那樣,它可能在功能期間使用該位置。 當您編寫讀取返回值的代碼(ch2 = toUpper(ch);) ,編譯器將編寫使用其約定的代碼,說明如何從常規位置檢索返回值。 就調用者代碼而言,即使未在其中明確寫入任何內容,它也只會從該位置讀取該值。 因此,您獲得了價值。

現在來看@Ray的示例,編譯器使用EAX寄存器存儲上套管操作的結果。 碰巧的是,這可能是返回值被寫入的位置。 在調用方ch2上加載了EAX中的值-因此是幻像返回。 這僅適用於x86系列處理器,因為在其他體系結構上,編譯器可能會使用完全不同的方案來決定如何組織約定。

但是,優秀的編譯器將根據局部條件,代碼知識,規則和啟發式方法嘗試進行優化。 因此要注意的重要一點是,這只是運氣。 編譯器可以優化而不執行此操作或執行其他操作-您不應就此行為進行回復。

您應該記住,取決於編譯器,此類代碼可能會崩潰。 例如,clang在此類函數的末尾生成ud2指令,您的應用程序將在運行時崩潰。

我嘗試了一個小程序:

#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());
}

結果:

測試:<1>

測試:<10>

測試:<11>

測試:<11>

測試:<11>

我使用了mingw32-gcc編譯器,因此可能會有差異。

您可以試玩一下,例如使用char函數。 只要您不使用結果值,它就可以正常工作。

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

但是我仍然建議設置void函數或提供一些返回值。

您的函數似乎需要返回:

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

沒有局部變量,因此函數末尾堆棧頂部的值將是參數c。 退出時位於堆棧頂部的值是返回值。 因此,無論c保持多少,這就是返回值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM