C++ inline 函数简介
1.inline 函數簡介
inline 函數由 inline 關鍵字定義,引入 inline 函數的主要原因是用它替代 C 中復雜易錯不易維護的宏函數。
2.編譯器對 inline 函數的處理辦法
編譯器在編譯階段完成對 inline 函數的處理,即對 inline 函數的調用替換為函數的本體。但 inline 關鍵字對編譯器只是一種建議,編譯器可以這樣去做,也可以不去做。從邏輯上來說,編譯器對 inline 函數的處理步驟一般如下:
(1)將 inline 函數體復制到inline函數調用處;
(2)為所用 inline 函數中的局部變量分配內存空間;
(3)將 inline 函數的的輸入參數和返回值映射到調用方法的局部變量空間中;
(4)如果 inline 函數有多個返回點,將其轉變為 inline 函數代碼塊末尾的分支(使用GOTO)。
比如如下代碼:
//求0-9的平方 inline?int?inlineFunc(int?num) {??if(num>9||num<0)return?-1;??return?num*num;?? }??int?main(int?argc,char*?argv[]) {int?a=8;int?res=inlineFunc(a);cout<<"res:"<<res<<endl; }inline 之后的 main 函數代碼類似于如下形式:
int?main(int?argc,char*?argv[]) {int?a=8;{??int?_temp_b=8;??int?_temp;??if?(_temp_q?>9||_temp_q<0)?_temp?=?-1;??else?_temp?=_temp*_temp;??b?=?_temp;??} }??經過以上處理,可消除所有與調用相關的痕跡以及性能的損失。inline 通過消除調用開銷來提升性能。
3.inline 函數使用的一般方法
函數定義時,在返回類型前加上關鍵字 inline 即把函數指定為內聯,函數申明時可加也可不加。但是建議函數申明的時候,也加上 inline,這樣能夠達到"代碼即注釋"的作用。
使用格式如下:
inline?int?functionName(int?first,?int?secend,...)?{/****/};inline如果只修飾函數的申明的部分,如下風格的函數foo不能成為內聯函數:
inline?void?foo(int?x,?int?y);?//inline僅與函數聲明放在一起void?foo(int?x,?int?y){}而如下風格的函數foo 則成為內聯函數:
void?foo(int?x,?int?y);inline?void?foo(int?x,?int?y){}?//inline與函數定義體放在一起4.inline 函數的優點與缺點
從上面可以知道,inline函數相對宏函數有如下優點:
(1)內聯函數同宏函數一樣將在被調用處進行代碼展開,省去了參數壓棧、棧幀開辟與回收,結果返回等,從而提高程序運行速度。
(2)內聯函數相比宏函數來說,在代碼展開時,會做安全檢查或自動類型轉換(同普通函數),而宏定義則不會。
例如宏函數和內聯函數:
使用宏函數時,其書寫語法也較為苛刻,如果對宏函數出現如下錯誤的調用,MAX(a,"Hello");?宏函數會錯誤地比較int和字符串,沒有參數類型檢查,但是使用內聯函數的時候,會出現類型不匹配的編譯錯誤。
(3)在類中聲明同時定義的成員函數,自動轉化為內聯函數,因此內聯函數可以訪問類的成員變量,宏定義則不能。
(4)內聯函數在運行時可調試,而宏定義不可以。
萬事萬物都有陰陽兩面,內聯函數也不外乎如此,使用inline函數,也要三思慎重。inline函數的缺點總結如下:
(1)代碼膨脹。
inline函數帶來的運行效率是典型的以空間換時間的做法。內聯是以代碼膨脹(復制)為代價,消除函數調用帶來的開銷。如果執行函數體內代碼的時間,相比于函數調用的開銷較大,那么效率的收獲會很少。另一方面,每一處內聯函數的調用都要復制代碼,將使程序的總代碼量增大,消耗更多的內存空間。
(2)inline 函數無法隨著函數庫升級而升級。
如果f是函數庫中的一個inline函數,使用它的用戶會將f函數實體編譯到他們的程序中。一旦函數庫實現者改變f,所有用到f的程序都必須重新編譯。如果f是non-inline的,用戶程序只需重新連接即可。如果函數庫采用的是動態連接,那這一升級的f函數可以不知不覺的被程序使用。
(3)是否內聯,程序員不可控。
inline函數只是對編譯器的建議,是否對函數內聯,決定權在于編譯器。編譯器認為調用某函數的開銷相對該函數本身的開銷而言微不足道或者不足以為之承擔代碼膨脹的后果則沒必要內聯該函數,若函數出現遞歸,有些編譯器則不支持將其內聯。
5.inline函數的注意事項
了解了內聯函數的優缺點,在使用內聯函數時,我們也要注意以下幾個事項和建議。
(1)使用函數指針調用內聯函數將會導致內聯失敗。
也就是說,如果使用函數指針來調用內聯函數,那么就需要獲取inline函數的地址。如果要取得一個inline函數的地址,編譯器就必須為此函數產生一個函數實體,那么就內聯失敗。
(2)如果函數體代碼過長或者有多重循環語句,if或witch分支語句或遞歸時,不宜用內聯。
(3)類的 constructors、destructors 和虛函數往往不是 inline 函數的最佳選擇。
類的構造函數(constructors)可能需要調用父類的構造函數,析構函數同樣可能需要調用父類的析構函數,二者背后隱藏著大量的代碼,不適合作為inline函數。虛函數(destructors)往往是運行時確定的,而inline是在編譯時進行的,所以內聯虛函數往往無效。如果直接用類的對象來使用虛函數,那么對有的編譯器而言,也可起到優化作用。
(4)至于內聯函數是定義在頭文件還是源文件的建議。
內聯展開是在編譯時進行的,只有鏈接的時候源文件之間才有關系。所以內聯要想跨源文件必須把實現寫在頭文件里。如果一個 inline 函數會在多個源文件中被用到,那么必須把它定義在頭文件中。
上面這種錯誤,就是因為內聯函數 fun() 定義在編譯單元 base.cpp 中,那么其他編譯單元中調用fun()的地方將無法解析該符號,因為在編譯單元 base.cpp 生成目標文件 base.obj 后,內聯函數fun()已經被替換掉,編譯器不會為 fun() 生成函數實體,鏈接器自然無法解析。所以如果一個 inline 函數會在多個源文件中被用到,那么必須把它定義在頭文件中。
這里有個問題,當在頭文件中定義內聯函數,那么被多個源文件包含時,如果編譯器因為 inline 函數不適合被內聯時,拒絕將inline函數進行內聯處理,那么多個源文件在編譯生成目標文件后都將各自保留一份inline函數的實體,這個時候程序在鏈接階段會出現重定義錯誤嗎?答案是不會,原因是,鏈接器在鏈接的過程中,會刪除多余的inline函數實體,只保留一份,所以不會報重定義錯誤,因此我們不需要使用 static 關鍵字去多余地修飾inline函數,即不必像下面這樣。
//test.h static?inline?int?max(int?a,int?b) {return?a>b?a:b; }(5)能否強制編譯器進行內聯操作?
也有人可能會覺得能否強制編譯器進行函數內聯,而不是建議編譯器進行內聯呢?很不幸的是目前還不能強制編譯器進行函數內聯,如果使用的是 MS VC++, 注意?__forceinline?如同 inine 一樣,也是一個用詞不當的表現,它只是對編譯器的建議比inline更加強烈,并不能強制編譯器進行inline操作。
(6)如何查看函數是否被內聯處理了?
在 VS2017 中查看預處理后的.i文件,發現inline函數的內聯處理不是在預處理階段,而是在編譯階段。將源文件編譯為匯編代碼,或者將可執行文件反匯編生成匯編代碼,在匯編代碼中查看inline函數被調用處是否出現匯編的call指令,如果沒有則說明inline函數在被調用處進行了函數體的替換操作,即內聯處理。具體可以參考內聯函數到底有沒有被嵌入到調用處呢?。
(7)C++類成員函數定義在類體內為什么不會報重定義錯誤?
類成員函數定義在類體內,并隨著類的定義放在頭文件中,當被不同的源文件包含,那么每個源文件都應該包含了類成員函數的實體,為何在鏈接的過程中不會報函數的重定義錯誤呢?
原因是在類里定義時,這種函數會被編譯器編譯成內聯函數,在類外定義的函數則不會。內聯函數的好處是加快程序的運行速度,缺點是會增加程序的尺寸。比較推薦的寫法是把一個經常要用的而且實現起來比較簡單的小型函數放到類里去定義,大型函數最好還是放到類外定義。
可能存在疑問,類體內的成員函數被編譯器內聯處理,但并不是所有的成員函數都會被內聯處理,比如包含遞歸的成員函數。但是實際測試,將包含遞歸的成員函數定義在類體內,被不同的源文件包含并不會報重定義錯誤,為什么會這樣呢?請保持著疑問與好奇心,請繼續往下看。
如果編譯器發現被定義在類體內的成員函數無法被內聯處理,那么在程序的鏈接過程中也不會出現函數重定義的錯誤。其原因是什么呢?其實很簡單,類體內定義的成員函數即使不被內聯處理,在鏈接時,鏈接器會對重復的成員函數實體進行冗余優化,只保留一份函數實體,也就不會出現函數重定義的錯誤了。
除了 inline 函數,C++編譯器在很多時候都會產生重復的代碼,比如模板(Templates)、虛函數表(Virtual Function Table)、類的默認成員函數(構造函數、析構函數和賦值運算符)等。以函數模板為例,在多個源文件中生成相同的實例,鏈接時不會出現函數重定義的錯誤,實際上是一個道理,因為鏈接器會對重復代碼進行刪除,只保留一份函數實體。
6.小結
可以將內聯理解為 C++ 中對于函數專有的宏,對于 C 的函數宏的一種改進。對于常量宏,C++ 提供 const 替代;而對于函數宏,C++ 提供的方案則是 inline。C++ 的內聯機制,既具備宏代碼的效率,又增加了安全性,還可以自由操作類的數據成員,算是一個比較完美的解決方案。
來源:https://dablelv.blog.csdn.net/article/details/52065524
總結
以上是生活随笔為你收集整理的C++ inline 函数简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 喵喵记账怎么删除账本
- 下一篇: C++抽象类