內聯函數:static inline 和 extern inline 的含義

前置簡短概述

引入內聯函數的目的是爲了解決程序中函數調用的效率問題。 

函數是一種更高級的抽象。它的引入使得編程者只關心函數的功能和使用方法,而不必關心函數功能的具體實現;函數的引入可以減少程序的目標代碼,實現程序代碼和數據的共享。但是,函數調用也會帶來降低效率的問題,因爲調用函數實際上將程序執行順序轉移到函數所存放在內存中某個地址,將函數的程序內容執行完後,再返回到轉去執行該函數前的地方。這種轉移操作要求在轉去前要保護現場並記憶執行的地址,轉回後先要恢復現場,並按原來保存地址繼續執行。因此,函數調用要有一定的時間和空間方面的開銷,於是將影響其效率。特別是對於一些函數體代碼不是很大,但又頻繁地被調用的函數來講,解決其效率問題更爲重要。引入內聯函數實際上就是爲了解決這一問題。 

在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來進行替換。顯然,這種做法不會產生轉去轉回的問題,但是由於在編譯時將函數休中的代碼被替代到程序中,因此會增加目標程序代碼量,進而增加空間開銷,而在時間代銷上不象函數調用時那麼大,可見它是以目標代碼的增加爲代價來換取時間的節省。

1.內聯函數可減少cpu的系統開銷,並且程序的整體速度將加快,但當內聯函數很大時,會有相反的作用,因此一般比較小的函數才使用內聯函數.
2.有兩種內聯函數的聲明方法,一種是在函數前使用inline關見字,另一種是在類的內部定義函數的代碼,這樣的函數將自動轉換爲內聯函數,而且沒必要將inline放在函數前面.
3.內聯是一種對編譯器的請求,下面這些情況會阻止編譯器服從這項請求.
如果函數中包含有循環,switch或goto語句,遞歸函數,含有static的函數.

由此可以看出,內聯函數和成員函數沒什麼區別,區別就在於怎樣加快函數的執行速度而已。

內聯函數是浪費空間來節省時間的設置,因爲函數的調用是很浪費時間的,寫成內聯函數可以在每次調用時用函數體內容代替函數調用,有點類似一個宏定義。當函數體語句較少,且沒有複雜的循環語句,且調用次數較多時,就可以用內聯函數。 

 

問:

首先,關於inline就夠煩人了,有的書上說inline關鍵字要加在定義前,聲明時可以省略,有的說聲明時加上inline函數就變成內聯型,有的說聲明和定義形式要保持一致。在一個類中聲明一個函數,函數的實現在外部,無論是僅僅在內部聲明處加inline,還是在外部實現處加inline,或是兩個地方都加,編譯均能通過,而且也無法通過調試的辦法看出對程序到底有啥影響。搞不清到底要怎麼寫這個inline才比較好,不過可以肯定的是,inline函數的定義部分要放在頭文件裏,聲明和定義分開放會編譯出錯。 

而且inline還可以和extern關鍵字、static關鍵字合用,在網上搜了一下,linux之父linus說過 "static inline" means "we have to have this function, if you use it, but don't inline it, then make a static version of it in this compilation unit". "extern inline" means "I actually _have_ an extern for this function, but if you want to inline it, here's the inline-version". 
這話說的雲裏霧裏的,誰能解釋一下,說說你對static inline 和 extern inline用法的理解。

答:

extern inline表示該函數是已聲明過的了.由於函數本身可以聲明多次,所以extern對函數的影響僅僅把函數的隱藏屬性顯式化了. 
extern 對於非函數的對象是有用的,因爲對象聲明時會帶來內存的分配,而用 extern就表示該對象已經聲明過了,不用再分配內存. 
static是以前C的用法.目的是讓該關鍵字標識的函數只在本地文件可見,同一個程序的其它文件是不可見該函數的.換句話說,就算你其它文件裏包含了同名同參數表的函數定義的話,也是不會引起函數重複定義的錯誤的.因爲static是僅在當前文件可見. 

關於inline函數,你說的大部分的都 是對的.我來給你總結一下吧. 
inline函數僅僅是一個建議,對編譯器的建議,所以最後能否真正內聯,看編譯器的意思,它如果認爲你的函數不復雜,能在調用點展開,就會真正內聯,並不是說聲明瞭內聯就會內聯,你聲明內聯只是一個建議而已. 
其次,因爲內聯函數要在調用點展開,所以編譯器必須隨處可見內聯函數的定義,要不然,就成了非內聯函數的調用了.所以,這要求你的每個調用了內聯函數的文件都出現了該內聯函數的定義,因此,將內聯函數放在頭文件裏實現是合適的,省卻你爲每個文件實現一次的麻煩.而你所以聲明跟定義要一致,其實是指,如果你在每個文件裏都實現一次該內聯函數的話,那麼,你最好保證每個定義都是一樣的,否則,將會引起未定義的行爲,即是說,如果不是每個文件裏的定義都一樣,那麼,編譯器展開的是哪一個,那要看具體的編譯器而定.所以,最好將內聯函數定義放在頭文件中. 
而類中的成員函數缺省都是內聯的,如果你在類定義時就在類內給出函數,那當然最好.如果你在類中未給出成員函數定義,而你又想內聯該函數的話,那在類外要加上inline,否則就認爲不是內聯的.而且剛說了,內聯函數最好放在頭文件內,所以最好在類定義的頭文件裏把類的內聯函數都實現了. 
而你說的將聲明與實現分開,其實是不會編譯出錯的,反正我寫那麼多程序都沒試過.將聲明與定義分開的話,這樣的後果會帶來編譯器並不隨處可見該函數定義,所以,只能在你實現定義的那個文件裏,將該函數看成內聯(如果可以內聯的話),在其它文件,仍看成是普通函數. 
看到這裏,我想你應該明白了.那麼聲明時加inline,實現時要不要加inline呢?呵呵,留給 lz 思考吧. 
  

呵呵,你看的還是英文版的哦.你理解的不是這樣,放在頭文件中不是隻存在一個定義,而是隻要包含了該頭文件的程序文本文件都存在了這個定義.而inline函數是可以被重複定義的,在C++中,常量對象跟內聯函數都是可以多次定義的. 
你要把函數那章都看完就會明白. 
我先假設你的函數符合內聯的條件. 
在聲明是加inline,定義時不加,則要求編譯器編譯時,能看到inline的聲明,而且在展開點看到該定義,這樣,就將其視爲內聯函數. 
如果你聲明沒有inline,卻在定義時inline了.這時,如果其它要調用該函數的文件看到了它的聲明,就認爲該函數不是內聯的,所以,到了調用處,轉到該函數實現的地方,卻意外地看到了inline聲明,這時,會導致鏈接出錯.若要改正的話,就要讓調用該函數的文件也看到有inline的定義,而不是在調用時纔看到.你可以在每個文件都加上有inline的定義.(如果不加inline,則會出現重複定義的錯誤,因爲內聯函數纔可以被重複定義).或者另一種修改方法,你將定義時的inline去掉,這樣就成爲普通函數,鏈接不會出錯.如果是前一種改法,仍是內聯的,因爲符合了看到了inline且隨處可見其定義的條件. 
如果你將聲明跟定義都放在同一個頭文件,而在聲明時不內聯,在實現時內聯,這樣編譯器也是將該函數內聯(符合兩個條件,看到inline的聲明(雖然是在定義時),隨處可見其定義). 
總結說來,只要編譯器看到有inline出現,而且定義隨處可見,就能將函數內聯(上邊已假設你的函數足夠簡單可以內聯),而不必管是定義還是聲明加inline的問題. 
所以,爲了方便,將內聯函數直接聲明時就定義,放在頭文件中.這樣其它文件包含了該頭文件,就在每個文件都出現了內聯函數的定義.就可以內聯了. 
類的成員函數也一樣.只不過,類的成員函數缺省都是內聯的,前提是你要在類定義時提供成員函數定義.如果在類定義時不提供函數定義,則要在類外邊加上inline,否則將視爲普通函數. 
關於這些,你看了C++primer有關函數那章自然會明白的. 

 

 

附:內聯函數作用及注意事項

定義

  內聯函數從源代碼層看,有函數的結構,而在編譯後,卻不具備函數的性質。編譯時,類似宏替換,使用函數體替換調用處的函數名。一般在代碼中用inline修飾,但是否能形成內聯函數,需要看編譯器對該函數定義的具體處理。
 

動機

  內聯擴展是用來消除函數調用時的時間開銷。它通常用於頻繁執行的函數。 一個小內存空間的函數非常受益。
 
  如果沒有內聯函數,編譯器可以決定哪些函數內聯 。 程序員很少或沒有控制哪些職能是內聯的,哪些不是。 給這種控制程度,作用是程序員可以選擇內聯的特定應用 。
 

函數內聯問題

  除了 ​​相關的問題, 內聯擴展一般,語言功能作爲一個內聯函數可能不被視爲有價值的,因爲它們出現的原因,對於一個數字:
 
  通常,一個編譯器是在一個比人類更有利的地位來決定某一特定功能是否應該被內聯。 有時,編譯器可能無法儘可能多的功能內嵌作爲程序員表示。
 
  一個重要的一點需要注意的是代碼(內聯函數)得到暴露其客戶端(調用函數)。
 
  隨着功能的演變,它們有可能成爲合適的內聯,他們不前,或不再在他們面前的內聯合適。 而內聯或取消內聯函數比從宏轉換爲更容易,但仍需要額外的維修,一般產量相對較少的利益。
 
  用於本機C型編譯系統的擴散可以增加編譯時間,因爲他們的身體的中間表示是到每個調用點,他們都是內聯複製內聯函數。在代碼大小可能增加是由在編譯時間可能增加鏡像。
 
  C99中內嵌的規範要求只有一個額外在另一個編譯單元,功能的外部定義時,相應的內聯定義,可以發生在不同的編譯單元多次,如果該函數用於地方。這很容易導致連接器,因爲這樣的定義不是由程序員提供的錯誤。 出於這個原因,往往是在C99內聯一起使用靜態的,也給出了函數的內部聯繫。
 
  在C + +,有必要定義一個在每一個模塊(編譯單元)內聯函數使用一個普通的功能,而必須在只有一個模塊中定義它。否則,就不可能編制的所有其他模塊一個模塊獨立。
 
  對於功能問題與優化本身,而不是語言,請參閱使用內聯擴展問題 。
 

行情

  “一個函數聲明[。。。]說明符聲明一個內聯與內聯函數。內聯說明符指示的實現,內聯函數體替代了在調用點是首選通常的函數調用機制。一個實現不要求在調用執行此點內聯替代,但是,即使這個內嵌替代省略,由7.1.2內聯函數定義的其他規則,仍應得到尊重“。
 
  - 國際標準化組織14882:1998(E)的,目前的C + +標準,第7.1.2
 
  “的函數說明符聲明的內聯函數是一個內聯函數。[。。。]製作一個內聯函數的函數表明該函數被調用儘可能快。在何種程度上這些建議是有效的,是實現定義( 注:例如,一個實施內聯替換可能不會執行,或者可能只執行替換內聯在聲明中要求的範圍內聯的)。
 
  “[。。。]內聯定義不提供外部定義的功能,並且不禁止的定義,還有一個是外部的翻譯單位。一個內聯定義提供了任何其他的外部定義,翻譯可能用來實現呼籲在相同的翻譯單元的功能。沒有指定是否調用該函數內聯定義或使用外部定義。“
 
  - 國際標準化組織9899:1999(E)的C99標準,第6.7.4
 

宏比較

  內聯函數的功能和預處理宏的功能相似。相信大家都用過預處理宏,我們會經常定義一些宏,如
 
  #define TABLE_COMP(x) ((x)>0?(x):0)
 
  就定義了一個宏。
 
  爲什麼要使用宏呢?因爲函數的調用必須要將程序執行的順序轉移到函數
 
  所存放在內存中的某個地址,將函數的程序內容執行完後,再返回到轉去執行
 
  該函數前的地方。這種轉移操作要求在轉去執行前要保存現場並記憶執行的地
 
  址,轉回後要恢復現場,並按原來保存地址繼續執行。因此,函數調用要有一
 
  定的時間和空間方面的開銷,於是將影響其效率。而宏只是在預處理的地方把
 
  代碼展開,不需要額外的空間和時間方面的開銷,所以調用一個宏比調用一個
 
  函數更有效率。
 
  但是宏也有很多的不盡人意的地方。
 
  1、.宏不能訪問對象的私有成員。
 
  2、.宏的定義很容易產生二意性。
 
  我們舉個例子:
 
  #define TABLE_MULTI(x) (x*x)
 
  我們用一個數字去調用它,TABLE_MULTI(10),這樣看上去沒有什麼錯誤,
 
  結果返回100,是正確的,但是如果我們用TABLE_MULTI(10+10)去調用的話,
 
  我們期望的結果是400,而宏的調用結果是(10+10*10+10),結果是120,這顯
 
  然不是我們要得到的結果。避免這些錯誤的方法,一是給宏的參數都加上括號。
 
  #define TABLE_MULTI(x) ((x)*(x))
 
  這樣可以確保不會出錯,但是,即使使用了這種定義,這個宏依然有可能
 
  出錯,例如使用TABLE_MULTI(a++)調用它,他們本意是希望得到(a+1)*(a+1)的
 
  結果,而實際上呢?我們可以看看宏的展開結果: (a++)*(a++),如果a的值是
 
  4,我們得到的結果是4*4 = 16,a = 6。而我們期望的結果是5*5=25,這又出現了問題。
 
  事實上,在一些C的庫函數中也有這些問題。例如:Toupper(*pChar++)就會對
 
  pChar執行兩次++操作,因爲Toupper實際上也是一個宏。
 
  我們可以看到宏有一些難以避免的問題,怎麼解決呢?
 
  下面就是用我要介紹的內聯函數來解決這些問題,我們可以使用內聯函數
 
  來取代宏的定義。而且事實上我們可以用內聯函數完全取代預處理宏。
 
  內聯函數和宏的區別在於,宏是由預處理器對宏進行替代,而內聯函數是
 
  通過編譯器控制來實現的。而且內聯函數是真正的函數,只是在需要用到的時
 
  候,內聯函數像宏一樣的展開,所以取消了函數的參數壓棧,減少了調用的開
 
  銷。你可以象調用函數一樣來調用內聯函數,而不必擔心會產生於處理宏的一
 
  些問題。
 
  我們可以用Inline來定義內聯函數,不過,任何在類的說明部分定義的函
 
  數都會被自動的認爲是內聯函數。
 
  下面我們來介紹一下內聯函數的用法。
 
  內聯函數必須是和函數體申明在一起,纔有效。像這樣的申明
 
  Inline Tablefunction(int I)是沒有效果的,編譯器只是把函數作爲普通的函
 
  數申明,我們必須定義函數體。
 
  Inline tablefunction(int I) {return I*I};
 
  這樣我們纔算定義了一個內聯函數。我們可以把它作爲一般的函數一樣調
 
  用。但是執行速度確比一般函數的執行速度要快。
 
  我們也可以將定義在類的外部的函數定義爲內聯函數,比如:
 
  Class TableClass{
 
  Private:
 
  Int I,j;
 
  Public:
 
  Int add() { return I+j;};
 
  Inline int dec() { return I-j;}
 
  Int GetNum();
 
  }
 
  inline int tableclass::GetNum(){
 
  return I;
 
  }
 
  上面申明的三個函數都是內聯函數。在C++中,在類的內部定義了函數體的
 
  函數,被默認爲是內聯函數。而不管你是否有inline關鍵字。
 
  內聯函數在C++類中,應用最廣的,應該是用來定義存取函數。我們定義的
 
  類中一般會把數據成員定義成私有的或者保護的,這樣,外界就不能直接讀寫我
 
  們類成員的數據了。
 
  對於私有或者保護成員的讀寫就必須使用成員接口函數來進行。如果我們把
 
  這些讀寫成員函數定義成內聯函數的話,將會獲得比較好的效率。
 
  Class sample{
 
  Private:
 
  Int nTest;
 
  Public:
 
  Int readtest(){ return nTest;}
 
  Void settest(int I) {nTest=I;}
 
  }
 
  當然,內聯函數也有一定的侷限性。就是函數中的執行代碼不能太多了,如
 
  果,內聯函數的函數體過大,一般的編譯器會放棄內聯方式,而採用普通的方式
 
  調用函數。這樣,內聯函數就和普通函數執行效率一樣了。
 

注意事項

  使用內聯函數應注意的事項
 
  內聯函數具有一般函數的特性,它與一般函數所不同之處只在於函數調用的處理。一般函數進行調用時,要將程序執行權轉到被調用函數中,然後再返回到調用它的函數中;而內聯函數在調用時,是將調用表達式用內聯函數體來替換。在使用內聯函數時,應注意如下幾點: 1.在內聯函數內不允許用循環語句和開關語句。 如果內聯函數有這些語句,則編譯將該函數視同普通函數那樣產生函數調用代碼,遞歸函數(自己調用自己的函數)是不能被用來做內聯函數的。內聯函數只適合於只有1~5行的小函數。對一個含有許多語句的大函數,函數調用和返回的開銷相對來說微不足道,所以也沒有必要用內聯函數實現。 2.內聯函數的定義必須出現在內聯函數第一次被調用之前。 3.本欄目講到的類結構中所有在類說明內部定義的函數是內聯函數。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章