C++基礎語法(一):關鍵字static, extern&inline含義與使用

        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". “誠如神之所說得意”,這句話已經深入淺出的區別了extern、static、inline三個關鍵的區別,如何理解這句話,帶我們回頭再來分析大笑

 

一、理解三個關鍵字需要的一些背景知識

1、源文件編譯->符號表->目標文件的鏈接

       在C/C++程序中,是以源文件爲單位編譯的,每個源文件會被編譯成成一個目標文件,目標文件中的變量或函數會生成一張符號表,這些符號不僅包括本文件中定義的函數或變量,還包括外部文件定義的函數或變量,符號對應的變量或函數地址將會在鏈接時一個符號決議的過程中確定,這裏需要說明的是,每個符號帶有一個鏈接屬性,鏈接屬性分三種,如下:

  • 外部鏈接(ExternalLinkage)
  • 具有External Linkage的標識符編譯後在符號表中是GLOBAL的符號。
  • 內部鏈接(InternalLinkage)
  • 具有Internal Linkage的標識符編譯後在符號表中是LOCAL的符號。
  • 無鏈接(NoLinkage)

除以上情況之外的標識符都屬於No Linkage的,例如函數的局部變量,以及不表示變量和函數的其它標識符。


2、C/C++變量內存分佈

       在C/C++中內存分爲:常量存儲區域、全局/靜態存儲區、堆區、棧區、自由存儲區,不同的變量或數據存儲在不同的區域,其中全局變量和靜態變量存儲在靜態存儲區,函數參數等變量存儲在棧區,new/delete的變量在自由存儲區,free/molloc的變量存儲在堆區(堆內存和自由存儲區,其實一般來講是同一塊內存區域,只不過堆是操作系統層面給出的概念,而自由存儲區纔是語言標準提供的概念,實際上目前多數編譯器的實現上自由存儲區就是堆區)。

可執行文件中分段結構爲:

  • .text: 也稱爲代碼段(Code),用來存放程序執行代碼,同時也可能會包含一些常量(如一些字符串常量等)。該段內存爲靜態分配,只讀(某些架構可能允許修改)。 這塊內存是共享的,當有多個相同進程(Process)存在時,共用同一個text段。

  • .data: 也有的地方叫GVAR(global value),用來存放程序中已經初始化的非零全局變量。靜態分配。".data"又可分爲讀寫(RW)區域和只讀(RO)區域。 

-> RW段則是普通非常全局變量,靜態變量就在其中
-> RO段保存常量所以也被稱爲.constdata 
  • .bss: 存放程序中爲未初始化的和零值全局變量。靜態分配,在程序開始時通常會被清零。(從可執行文件的角度來講,如果一個數據未被初始化那就不需要爲其分配空間,所以.data和.bss一個重要的區別就是.bss並不佔用可執行文件的大小,它只是記載需要多少空間來存儲這些未初始化數據,而不分配實際的空間)

  • Stack: 棧,存放Automatic Variables,按內存地址由高到低方向生長,其最大大小由編譯時確定,速度快,但自由性差,最大空間不大。

  • Heap: 堆,自由申請的空間,按內存地址由低到高方向生長,其大小由系統內存/虛擬內存上限決定,速度較慢,但自由性大,可用空間大。 (每個線程都會有自己的棧,但是堆空間是共用的。)

 

二、static, extern&inline表示的含義

1、static、extern與inline修飾的含義

static: 該關鍵字修飾的全局變量或函數具有內鏈接屬性,所以不可被其他文件引用,所以好處就是即使外部文件具有同名函數或變量也不會發生重命名衝突。此外,當static修飾函數內的局部變量的時候,變量在GVAR內存區開闢內存,也即即使函數執行完成,堆棧釋放而static變量不會被釋放,但需要注意的是即使變量在全局區,但是可見域並沒有改變,只有函數內可見。

extern:該關鍵子字修飾全局變量或內存具有外鏈接屬性,通過外部文件聲明可以在外部文件引用,一般情況下的函數或全局變量,默認爲extern屬性。

inline:inline其實和鏈接屬性沒有直接的關係,且只用來修飾函數,經過inline修飾的函數,實時上是對編譯器做代碼展開建議,如果代碼複雜度較高,inline並不會生效。因爲內聯函數要在調用點展開,所以編譯器必須隨處可見內聯函數的定義,要不然就成了普通函數的調用了,所以將內聯函數的定義放在頭文件裏實現是合適的,省卻你爲每個文件實現一次的麻煩。如果你真的打算每個文件裏都實現一次該內聯函數的話,那麼,最好保證每個定義都是一樣的,否則行爲是未定義的。

Q:既然有可能inline函數產生與普通函數相似的調用效果,那麼會在符號表產生符號嗎?在頭文件定義會出現重定義嗎?

A:會產生符號,函數均會產生符號,但是默認是內鏈接屬性的符號,所以不會重定義。

const:const修飾變量屬性是內鏈接的,也即在頭文件定義不會造成重定義,但可以通過extern 雙重修飾,是變量具有外鏈接屬性,此時定義不能放在頭文件(避免重定義),const也可以用來修飾類的成員函數,此時類不能修改對象的內容。

2、inline與宏的區別

優點:

1)inline定義的內聯函數,函數代碼被放入符號表中,在使用時進行替換(像宏一樣展開),效率很高。

2)類的內聯函數也是函數。編繹器在調用一個內聯函數,首先會檢查參數問題,保證調用正確,像對待真正函數一樣,消除了隱患及侷限性。

3)inline可以作爲類的成員函數,刀可以使用所在類的保護成員及私有成員。

缺點:

內聯函數以複製爲代價,活動產函數開銷

1)如果函數的代碼較長,使用內聯將消耗過多內存

2)如果函數體內有循環,那麼執行函數代碼時間比調用開銷大。

 

三、再理解神之所說

回到開篇的問題,static inline首先我們必須認識到,這是一個static函數,也即函數是文件內鏈接的,定義放在頭文件,即使多處包含也不會出現重定義問題,如果你將它放進源文件反而會出錯,因爲這時其他文件看不到定義,所以必須定義在頭文件,然後在調用處將會做內斂展開。而extern inline首先函數是extern的,也就是說函數可能是從其他文件引入的,如果本文件看不到定義,則只會做普通函數調用,如果能看到定義,則可以嘗試inline展開。

 

Reference:

extern與static的鏈接屬性

static與extern修飾變量

c++內存分配方式

static與inline關鍵字


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章