C/C++編譯器是否會無條件消除空函數調用?

一個函數接受一些參數但是返回void,整個函數體直接留空,那麼調用這樣的函數的調用語句會被編譯器整個消除嗎?包括參數傳遞所引發的類構造函數調用也會被消除嗎?再假如參數全是POD類型呢?再假如參數不都是POD但全部是基本類型或具有default copy constructor呢?


題主的問題:

C/C++編譯器是否會無條件消除空函數調用?

顯然不會「無條件」地做。這全看編譯器的心情以及指定的優化級別。像TCC那樣超級簡單的C編譯器就不會做題主所想像的優化,無論指定什麼優化級別。像GCC、Clang在-O0也肯定不會做這樣的優化。

如果討論的問題是「C/C++編譯器是否有能力消除空函數調用」或者「在默認優化級別下是否會消除空函數調用」,那又是另一回事。

消除一個空函數調用,跟內聯這個空函數調用是等價的。而一個函數是否能內聯最重要的一點是編譯器是否有足夠信息靜態確定調用的目標函數是誰。對於普通的C/C++全局函數的直接調用或者非虛成員函數的直接調用,這事情都是很簡單直觀的;對於C++的虛函數,或者對於通過函數指針來做的間接函數調用,這事情就沒那麼簡單了,得看情況。

另外就是,同學們在想像編譯器是如何優化代碼的時候,最好能知道如何把一個看似是一個整體的東西,拆分成許多細粒度的小東西,然後逐步迭代去優化它,經過多個步驟後得到最終優化結果。這樣每個小步驟就很容易思考和討論,而不會像直接討論一個大東西「是否能被優化掉」那麼奇怪。

例如說,一個C/C++源碼層面上的函數調用,實際實現會由若干小東西構成:

  1. 對參數列表裡的實際參數的表達式逐一求值
  2. 將實際參數的值根據調用約定傳遞到指定的目標位置上
  3. (如果需要的話)查找目標函數的具體位置
  4. 調用目標函數,將控制跳轉到目標函數的入口處
  5. 進入目標函數里執行
  6. 目標函數根據調用約定將返回值傳遞到指定的目標位置上
  7. 從目標函數返回,控制跳轉回到調用者的一側

如果編譯器有足夠信息確定目標函數是誰,能夠對目標函數內聯的話,那麼上述步驟中(3)、(4)、(6)、(7)就都不需要了;

然後如果目標函數是空函數,那麼(5)就是空的;

這樣就可以發現(1)中求出來的值在後面都沒有被任何代碼使用到,因而(1)與(2)就是死代碼而可以被消除。但是如果(1)中參數求值涉及帶有副作用的操作(例如說調用個printf()啥的),這些副作用必須要留下來而不能被隨意優化掉。


會不會不敢打包票,可能是可能的。編譯器優化的餘地是很大的,底線就是你變數之間的依賴,這個不會變

另外C++不保證對象一定會構造或者拷貝什麼的,如我上面所說,編譯器可以刪掉或者重排你的代碼,只要不違背你的對象之間的依賴關係


說句題外話:

int main(){

int func();

if(0==1)func();

return 0;

}

GCC4.5可以鏈接ok, 而VS會報找不到func的鏈接錯誤。。。注意這和sizeof(func)不一樣,後者無條件不會產生鏈接func的需求。制定C/C++標準的人有點腦殘。。傳說中的C++17可以解決。

所以我推薦問題主可以加上問題分支:編譯器是否會無條件消除不會進入的代碼塊中的函數調用,所產生的鏈接和動態鏈接需求?


聲明: 我完全不懂編譯原理

-------------------------------

首先不能依賴編譯器優化, 這個是實現細節不是語言規定.

然後談正事:

C++里有個概念叫做inline function, 在語言規定里, 即使你不使用inline關鍵字,編譯器也可以視心情決定要不要內聯你的函數. 如果函數體位空, 又被內聯了, 顯然這個函數調用就不會產生任何機器碼.

以上完全是靠推理得到的結論, 沒權威價值.

其實我只是想鄙視下題主連這種基本的推理都不做, 就信口提問.


推薦閱讀:

是否有去除c++多餘頭文件的工具?
用 Visual Studio 2013 能學好 C++ 嗎?
使用Visual studio 2015 找不見C++?
C++類型問題,typeid與sizeof運算符的差異?
C++11(VC++) 中支持多種for循環寫法,哪種比較好?

TAG:計算機科學 | C | 編譯器 |