欧美一区二区三区,国内熟女精品熟女A片视频小说,日本av网,小鲜肉男男GAY做受XXX网站

書上說inline函數可以代替宏是什么意思

錢艷冰2年前21瀏覽0評論

書上說inline函數可以代替宏是什么意思?

謝邀。

打開 Linux 內核源代碼,會發現內核在定義C語言函數時,有很多都帶有 “inline”關鍵字,請看下圖,那么這個關鍵字有什么作用呢?

inline 關鍵字的作用

在C語言程序開發中,inline 一般用于定義函數,inline 函數也被稱作“內聯函數”,C99 和 GNU C 均支持內聯函數。那么在C語言中,內聯函數和普通函數有什么不同呢?其實,從 inline 這個名字就應該能看出一點它的性質了——內聯函數會在它被調用的位置上展開,這一點表現的和 define 宏定義是非常相似的。

將被調用的函數代碼展開,操作系統就無需再在為被調用函數做申請棧幀和回收棧幀的工作,而且,由于編譯器會把被調用的函數代碼和函數本身放在一起優化,所以也有進一步優化C語言代碼,提升效率的可能。

每發生一次函數調用,操作系統就要在程序的棧空間申請一塊內存區域(棧幀),供被調用函數使用,被調用函數執行完畢后,操作系統還要回收這些內存。

不過,天下沒有免費的午餐,C語言程序要實現內聯函數的上述特性是要付出一定的代價的。普通函數只需要編譯出一份,就可以被所有其他函數調用,而內聯函數沒有嚴格意義上的“調用”,它只是將自身的代碼展開到被調用處的,這么做無疑會使整個C語言代碼變長,也就意味著占用更多的內存空間,以及更多的指令緩存。

顯然,如果濫用內聯函數,cpu 的指令緩存肯定是不夠用的,這會導致 cpu 緩存命中率降低,反而可能會降低整個C語言程序的效率。因此,建議把那些對時間要求比較高,且C語言代碼長度比較短的函數定義為內聯函數。如果在C語言程序開發中的某個函數比較大,又會被反復調用,并且沒有特別的時間限制,是不適合把它做成內聯函數的。

在 Linux 內核中,內聯函數常常使用 static 修飾,例如:

需要注意的是,內聯函數必須在使用之前就定義好,否則編譯器沒法把這個函數展開。Linux 內核中經常像下面這樣,將內聯函數放在調用它的函數前面,請看C語言代碼:

所以,Linux 內核常常把內聯函數定義在頭文件里,這樣在其他C語言代碼文件開頭包含頭文件時,能確保內聯函數在文件的最開始,無需再寫額外的聲明語句。

這也解釋了為什么 Linux 內核為何常常使用 static 修飾內聯函數,因為可以避免函數的重復定義。

前文提到內聯函數的表現有些像 define 宏定義,但是為了類型安全和易讀性,應優先使用內聯函數而不是復雜的宏。下面通過實例進一步分析 inline 內聯函數的特性。

inline內聯函數的“展開代碼”是什么意思?

使用過 define 寫 C語言代碼的朋友應該都知道,編譯器在編譯 C語言代碼時,會將 define 定義的宏展開,而不是像普通函數那樣使用 call 指令調用,例如下面這段C語言代碼:

使用 gcc -E 編譯這段C語言代碼,能夠得到預處理后的代碼如下,顯然 define 定義的宏被展開了,請看:

使用 gcc -g 命令編譯C語言代碼,得到可執行文件,然后調用 objdump 命令查看匯編代碼,得到如下結果:

從 f_add() 函數的匯編代碼也可以看出,程序首先將 2 個參數賦值給寄存器,然后使用 call 指令調用 f_add() 函數。而宏定義 d_add() 就簡單了,只有一行匯編代碼,這種情況下,使用 define 宏定義顯然效率更高。不過,宏定義沒有參數的類型檢查,使用起來不太安全,好在C語言還有 inline 函數,下面再定義一個 inline 函數,請看C語言代碼如下:

在 main() 函數中使用 gcc -E 命令查看添加 inline 函數后的C語言代碼預處理結果,如下:

可以看出,在預處理階段,inline 函數并沒有像 define 宏那樣展開。現在使用 gcc -g 命令編譯得到可執行文件,然后使用 objdump 查看匯編代碼,如下:

從匯編代碼可以看出,inline 函數似乎并沒有起到作用,i_add() 函數和 f_add() 函數的表現并沒有什么不同,繼續往上查看,發現編譯器也將 i_add() 函數的匯編代碼生成了,這無疑是將 i_add() 函數當作普通函數使用了:

怎么回事?不是說 inline 函數的表現和 define 宏相似,會將函數代碼展開嗎?其實,inline 只是建議編譯器這么做,編譯器究竟會不會這么做就不一定了。這與編譯器的優化級別相關,請看下圖:

gcc 的 -O 選項可以指定優化級別,我們上面編譯程序時沒有使用 -O 選項,因此編譯器執行的是默認的 -O0,也即無優化編譯。那能否在 -O0 優化級別也使用 inline 函數的特性呢?當然是可以的,只需要在定義 inline 函數時,添加 __attribute__((always_inline)) 即可,例如:

現在再來編譯C語言程序并查看匯編代碼,得到如下結果:

這種情況下,編譯器并沒有為 i_add() 函數生成響應的匯編代碼。雖然 inline 函數在預處理階段沒有像 define 宏定義那樣展開,但是在生成匯編代碼階段展開了,而且參與了調用它的代碼部分的優化,這顯然會讓整個C語言程序的效率提高。

inline 函數雖然表現上很像 define 宏定義,但是卻并不能完全取代 define 宏定義,這一點在我之后的文章里會討論,敬請關注。

小結

在 C語言程序開發中,建議把那些對時間要求比較高,且C語言代碼長度比較短的函數定義為 inline 函數,這么做常常可以提升程序的效率。在默認的 -O0 編譯優化項不能確保 inline 一定起作用,但是可以添加添加 __attribute__((always_inline))強制編譯器對 inline 函數做相應的處理。因為 inline 函數會將自己展開,所以編譯器通常不會再為 inline 生成匯編代碼,不過,如果是通過函數指針的形式調用 inline 函數,編譯器為了獲得 inline 函數的地址,仍然會為其生成匯編代碼的。

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。