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

前置簡短概述linux

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

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

在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來進行替換。顯然,這種作法不會產生轉去轉回的問題,可是因爲在編譯時將函數休中的代碼被替代到程序中,所以會增長目標程序代碼量,進而增長空間開銷,而在時間代銷上不象函數調用時那麼大,可見它是以目標代碼的增長爲代價來換取時間的節省。
 
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用法的理解。編程

答:ide

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有關函數那章天然會明白的. 優化

 

 

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

定義

  內聯函數從源代碼層看,有函數的結構,而在編譯後,卻不具有函數的性質。編譯時,相似宏替換,使用函數體替換調用處的函數名。通常在代碼中用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)
 
  就定義了一個宏。
 
  爲何要使用宏呢?由於函數的調用必需要將程序執行的順序轉移到函數
 
  所存放在內存中的某個地址,將函數的程序內容執行完後,再返回到轉去執行
 
  該函數前的地方。這種轉移操做要求在轉去執行前要保存現場並記憶執行的地
 
  址,轉回後要恢復現場,並按原來保存地址繼續執行。所以,函數調用要有一
 
  定的時間和空間方面的開銷,因而將影響其效率。而宏只是在預處理的地方把
 
  代碼展開,不須要額外的空間和時間方面的開銷,因此調用一個宏比調用一個
 
  函數更有效率。
 
  可是宏也有不少的不盡人意的地方。
 
  一、.宏不能訪問對象的私有成員。
 
  二、.宏的定義很容易產生二意性。
 
  咱們舉個例子:
 
  #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.本欄目講到的類結構中全部在類說明內部定義的函數是內聯函數。
相關文章
相關標籤/搜索