[英]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.