函數調用過程
c++經過編譯生成可執行程序文件exe,存放在外存儲器中。程序啟動,系統從外存儲器中將可執行文件裝載到內存中,從入口地址(main函數起始處)開始執行。程序執行中遇到了對其他函數的調用,就暫停當前函數的執行,并保存下一條指令的地址作為從被調函數返回后繼續執行的入口點,保存現場。然后轉到被調函數的入口地址執行被調函數。遇到return語句或者被調函數結束后,恢復先前保存的現場,從先前保存的返回地址處繼續執行主調函數的其余部分。
內聯函數
函數調用需要進行現場保護,以便在函數調用之后繼續進行。函數調用后還需要恢復現場才能繼續執行。這都需要系統開銷,影響了程序的效率。
內聯函數在編譯的時候將所調用的函數代碼直接嵌入到主調函數中,定義方式就是在普通的函數定義前面加上inline,不存在程序流程跳轉和返回,但是增加了程序代碼。內聯函數函數體不能含有復雜的結構控制語句,適用于1-5行的小函數。當函數規模比較大的時候,函數運行的時間相對與函數的調用和返回時間大很多,綜合時間和空間考慮,用內聯沒有太大意義。
原理:
對于任何內聯函數,編譯器在符號表里放入函數的聲明(包括名字、參數類型、返回值類型)。如果編譯器沒有發現內聯函數存在錯誤,那么該函數的代碼也被放入符號表里。在調用一個內聯函數時,編譯器首先檢查調用是否正確(進行類型安全檢查,或者進行自動類型轉換,當然對所有的函數都一樣)。如果正確,內聯函數的代碼就會直接替換函數調用,于是省去了函數調用的開銷。
內聯函數與宏的區別
1.內聯函數在運行時可調試,而宏定義不可以;
2.編譯器會對內聯函數的參數類型做安全檢查或自動類型轉換(同普通函數),而宏定義則不會;
3.內聯函數可以訪問類的成員變量,宏定義則不能;
4.在類中聲明同時定義的成員函數,自動轉化為內聯函數。
C++ 語言的函數內聯機制既具備宏代碼的效率,又增加了安全性,而且可以自由操作類的數據成員。所以在C++ 程序中,應該用內聯函數取代所有宏代碼
一個可執行文件的cpp文件中一個函數只能被定義一次。如果你把函數定義在一個.h文件中并讓兩個cpp包含就會造成這個函數分別在兩個cpp中被定義產生錯誤。但是inline函數是允許在多個cpp中多次定義的,就解決了這個問題。
for (int i=v.begin() ; i<v.size() ; i++) { .... }
對于size()的調用,其實是內聯。在循環時,可以采用變量保存v.size()的值,以減少每個循環的調用開支。于是決定一搜,順便總結之。
1、inline的引出
考慮下列min()函數
int min( int v1, int v2 ) { return( v1 < v2 << v1 : v2 ); }
為這樣的小操作定義一個函數的好處是:
a.如果一段代碼包含min()的調用,那閱讀這樣的代碼并解釋其含義比讀一個條件操作符的實例,可讀性會強很多。
b.改變一個局部化的實現比更改一個應用中的300個出現要容易得多
c.語義是統一的,每個測試都能保證相同的方式實現
d.函數可以被重用,不必為其他的應用重寫代碼
不過,將min()寫成函數有一個嚴重的缺點:調用函數比直接計算條件操作符要慢很多。那怎么能兼顧以上優點和效率呢?C++提供的解決方案為inline(內聯)函數
2、inline的原理:代碼替代
在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來進行替代。
例如,如果一個函數被指定為inline 函數則它將在程序中每個調用點上被內聯地展開例如
int minVal2 = min( i, j );
在編譯時被展開為
int minVal2 = i < j << i : j;
則把min()寫成函數的額外執行開銷從而被消除了。
3、inline的使用
讓一個函數成為內聯函數,隱式的為在類里定義函數,顯式的則是在函數前加上inline關鍵字說明。
4、使用inline的一些注意事項
a.從inline的原理,我們可以看出,inline的原理,是用空間換取時間的做法,是以代碼膨脹(復制)為代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。如果執行函數體內代碼的時間,相比于函數調用的開銷較大,那么效率的收獲會很少。所以,如果函數體代碼過長或者函數體重有循環語句,if語句或switch語句或遞歸時,不宜用內聯
b.關鍵字inline 必須與函數定義體放在一起才能使函數成為內聯,僅將inline 放在函數聲明前面不起任何作用。內聯函數調用前必須聲明。
inline void Foo(int x, int y); // inline 僅與函數聲明放在一起 void Foo(int x, int y) { ... }
以上代碼不能成為內聯函數,而以下則可以
void Foo(int x, int y); inline void Foo(int x, int y) // inline 與函數定義體放在一起 { ... }
所以說,inline 是一種“用于實現的關鍵字”,而不是一種“用于聲明的關鍵字”。對于以上例子,林銳還建議,只在定義前加上inline,而不是在聲明和定義前都加,因為這能體現高質量C++/C 程序設計風格的一個基本原則:聲明與定義不可混為一談。
c.inline對于編譯器來說只是一個建議,編譯器可以選擇忽略該建議。換句話說,哪怕真的寫成了inline,也沒有任何錯誤的情況下,編譯器會自動進行優化。所以當inline中出現了遞歸,循環,或過多代碼時,編譯器自動無視inline聲明,同樣作為普通函數調用。
總結下:
覺得可以將內聯理解為C++中對于函數專有的宏,對于C的函數宏的一種改進。對于常量宏,C++提供const替代;而對于函數宏,C++提供的方案則是inline。在C中,大家都知道宏的優勢,編譯器通過復制宏代碼的方式,省去了參數壓棧,生成匯編的call調用,返回參數等操作,雖然存在一些安全隱患,但在效率上,還是很可取的。
不過函數宏還是有不少缺陷的,主要有以下:
a.在復制代碼時,容易出現一想不到的邊際效應,比如經典的
#define MAX(a, b) (a) > (b) ? (a) : (b)
在執行語句:
result = MAX(i, j) + 2 ;
時,會被解釋為
result = (i) > (j) ? (i) : (j) + 2 ;
b.使用宏,無法進行調試,雖然windows提供了ASSERT宏
c.使用宏,無法訪問類的私有成員
所以,C++ 通過內聯機制,既具備宏代碼的效率,又增加了安全性,還可以自由操作類的數據成員,算是一個比較完美的解決方案。