2015-12-15 54 views
4

正如標題的功能,會發生什麼,如果我有:什麼編譯器做的只包含一個函數調用

void a(uint8_t i) { 
    b(i, 0); 
} 

將編譯器能夠代替調用一個(i)與b(i,0)?

此外,在這兩種情況下,將以下被認爲是很好的做法,以取代上述:

#define a(i) b(i, 0) 
+3

前者是「tail call elimination」,這是一種已知的優化策略;後者只是不是不。 –

+0

如果在使用時可以看到'a()'的定義,或者構建系統被指示執行鏈接時間或整個程序優化,那麼它會這麼做(還有更多)。 (這意味着如果使用標準構建系統,a()不能內聯,如果它駐留在不同的翻譯單元中)。 –

回答

3

這很容易測試。如果對a的調用位於同一個編譯單元中,大多數編譯器都會優化它。讓我們看看會發生什麼:

$ cat > foo.c 
void b(int, int); 

void 
a(int a) 
{ 
     b(a, 0); 
} 

void 
foo(void) 
{ 
     a(17); 
} 

然後編譯它只是一些基本的優化彙編(我加省略幀指針創建乾淨的輸出,你可以驗證同樣的事情會發生沒有該標誌):

$ cc -fomit-frame-pointer -S -O2 foo.c 

再看看輸出(I清理了,只是不停的代碼中,有一個在生成的彙編,這裏是不相關的大量的註解):

$ cat foo.s 
a: 
    xorl %esi, %esi 
    jmp b 
foo: 
    xorl %esi, %esi 
    movl $17, %edi 
    jmp b 

所以我們可以在這裏看到,編譯器首先生成了一個調用b的正常函數a(除了它的尾部調用優化之外,所以它是jmp而不是調用)。然後在編譯foo而不是調用a時,它只是將其內聯。

我在這種情況下使用的編譯器是gcc的一個相對舊版本,我也檢查過clang是否完全一樣。這是非常標準的優化,只要編譯器做任何內聯,像這樣的簡單函數將始終內聯。

1

編譯器將最有可能優化該代碼,並使其成爲inline function

inline void a(uint8_t i) { 
    b(i, 0); 
} 

所以像a(i)這樣的電話確實將被替換爲b(i, 0)

+0

它不會,因爲必須在全局符號表中導出「a」,它可能會被引用從「外部」 – Ctx

+2

@Ctx它可能會做@ –

+1

@MM理論上,也許(沒有想到通過)。你知道一個編譯器,它真的會這麼做嗎? – Ctx

2

它取決於一些東西,其中最重要的是你選擇的工具鏈(編譯器,鏈接器等)和優化設置。

如果編譯器具有a()定義的可見性 - 不僅僅是聲明 - 它可能會選擇內聯a()。編譯器不需要這樣做,但根據優化設置和編譯器本身的實現質量,它可能會這樣做。然而,你的情況對於現代編譯器來說是一個相當普遍和直接的優化。

如果該函數沒有被聲明爲static(它過於簡單化使得它在特定編譯單元中是本地的),那麼大多數編譯器仍然會在目標文件中保留函數a()的定義,因此它可以與其他目標文件(用於其他編譯單元)。即使它選擇在定義它的編譯單元內嵌入函數的調用。

如果該函數被聲明爲inline(且編譯器具有該定義的可見性),則實際上適用相同的情況。 inline是標準允許編譯器忽略的提示,無論程序員是多麼強硬。在實踐中,現代編譯器通常可以更好地決定哪些函數內聯,而不是程序員可以。

如果您的代碼存儲了地址a()(例如,在指向函數的指針中),編譯器可能會選擇不內聯它。

即使編譯器不內聯函數,智能鏈接器也可能選擇(實際上)內聯它。然而,大多數C實現使用傳統的啞連接器作爲工具鏈的一部分 - 所以這種類型的鏈接時優化在實踐中不太可能。

即使鏈接器沒有,一些虛擬機主機環境可能會選擇在運行時內聯。這對於C程序來說是非常不尋常的,但不會超越可能性。

就我個人而言,我不會擔心它。編譯器是否會執行這種優化方式,除非您擁有真正的大量這樣的函數,否則幾乎沒有可觀察到的差異(例如程序性能,大小等)。

我不會使用宏。如果你真的不想在使用b()時輸入, 0,那麼只需編寫你的函數a(),讓編譯器擔心它。如果性能測量和性能分析顯示您的功能,只嘗試進一步手動優化a()是性能熱點。它可能不會。

或者,使用C++,併爲第二個參數聲明函數b(),其默認值爲0。 ;)

+0

不再那麼不尋常了。 gcc擁有-flto(https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html),如果提供給單個gcc調用,則跨翻譯單元進行優化。 –

相關問題