正如標題的功能,會發生什麼,如果我有:什麼編譯器做的只包含一個函數調用
void a(uint8_t i) {
b(i, 0);
}
將編譯器能夠代替調用一個(i)與b(i,0)?
此外,在這兩種情況下,將以下被認爲是很好的做法,以取代上述:
#define a(i) b(i, 0)
正如標題的功能,會發生什麼,如果我有:什麼編譯器做的只包含一個函數調用
void a(uint8_t i) {
b(i, 0);
}
將編譯器能夠代替調用一個(i)與b(i,0)?
此外,在這兩種情況下,將以下被認爲是很好的做法,以取代上述:
#define a(i) b(i, 0)
這很容易測試。如果對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是否完全一樣。這是非常標準的優化,只要編譯器做任何內聯,像這樣的簡單函數將始終內聯。
編譯器將最有可能優化該代碼,並使其成爲inline function:
inline void a(uint8_t i) {
b(i, 0);
}
所以像a(i)
這樣的電話確實將被替換爲b(i, 0)
它取決於一些東西,其中最重要的是你選擇的工具鏈(編譯器,鏈接器等)和優化設置。
如果編譯器具有a()
定義的可見性 - 不僅僅是聲明 - 它可能會選擇內聯a()
。編譯器不需要這樣做,但根據優化設置和編譯器本身的實現質量,它可能會這樣做。然而,你的情況對於現代編譯器來說是一個相當普遍和直接的優化。
如果該函數沒有被聲明爲static
(它過於簡單化使得它在特定編譯單元中是本地的),那麼大多數編譯器仍然會在目標文件中保留函數a()
的定義,因此它可以與其他目標文件(用於其他編譯單元)。即使它選擇在定義它的編譯單元內嵌入函數的調用。
如果該函數被聲明爲inline
(且編譯器具有該定義的可見性),則實際上適用相同的情況。 inline
是標準允許編譯器忽略的提示,無論程序員是多麼強硬。在實踐中,現代編譯器通常可以更好地決定哪些函數內聯,而不是程序員可以。
如果您的代碼存儲了地址a()
(例如,在指向函數的指針中),編譯器可能會選擇不內聯它。
即使編譯器不內聯函數,智能鏈接器也可能選擇(實際上)內聯它。然而,大多數C實現使用傳統的啞連接器作爲工具鏈的一部分 - 所以這種類型的鏈接時優化在實踐中不太可能。
即使鏈接器沒有,一些虛擬機主機環境可能會選擇在運行時內聯。這對於C程序來說是非常不尋常的,但不會超越可能性。
就我個人而言,我不會擔心它。編譯器是否會執行這種優化方式,除非您擁有真正的大量這樣的函數,否則幾乎沒有可觀察到的差異(例如程序性能,大小等)。
我不會使用宏。如果你真的不想在使用b()
時輸入, 0
,那麼只需編寫你的函數a()
,讓編譯器擔心它。如果性能測量和性能分析顯示您的功能,只嘗試進一步手動優化a()
是性能熱點。它可能不會。
或者,使用C++,併爲第二個參數聲明函數b()
,其默認值爲0
。 ;)
不再那麼不尋常了。 gcc擁有-flto(https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html),如果提供給單個gcc調用,則跨翻譯單元進行優化。 –
前者是「tail call elimination」,這是一種已知的優化策略;後者只是不是不。 –
如果在使用時可以看到'a()'的定義,或者構建系統被指示執行鏈接時間或整個程序優化,那麼它會這麼做(還有更多)。 (這意味着如果使用標準構建系統,a()不能內聯,如果它駐留在不同的翻譯單元中)。 –