C語言中的Static和內(nèi)聯(lián)函數(shù)
1、在C語言中,關(guān)鍵字Static有三個明顯的作用:
--- 在函數(shù)體內(nèi),一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用結(jié)束后不釋放其存儲空間。定義為static的局部變量存儲在全局區(qū)(靜態(tài)區(qū)),而一般的局部變量存儲在棧中。
--- 在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所有函數(shù)訪問,但不能被模塊外其他函數(shù)訪問。它是一個本地的全局變量。在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。即這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
--- 類中定義的static數(shù)據(jù)成員屬于所有該類對象共享,在內(nèi)存中只占一份空間,而不是每個對象都分別為它保留一份空間。
--- 類中定義為static的成員函數(shù)只能直接調(diào)用static數(shù)據(jù)成員,若要訪問非靜態(tài)數(shù)據(jù)成員,需要加上對象名,因為靜態(tài)成員函數(shù)沒有this指針。
2、內(nèi)聯(lián)函數(shù)需要注意點
--- 可將代碼很少的函數(shù)定義為inline函數(shù)
--- 不要將代碼很多的函數(shù)定義為inline函數(shù)
--- 關(guān)鍵字inline必須與函數(shù)定義體放在一起才能使函數(shù)稱為內(nèi)聯(lián)
--- 僅將inline放在函數(shù)聲明前面不起作用
--- 定義在類聲明之中的成員函數(shù)將自動地成為內(nèi)聯(lián)函數(shù)
--- 現(xiàn)在的編譯器會自動決定是否對函數(shù)inline,無論函數(shù)前是否加了inline。
3、程序的組成部分
由C語言代碼(文本文件)形成可執(zhí)行程序(二進制文件),需要經(jīng)過編譯 - 匯編 - 連接三個階段。
編譯過程把C語言文本文件生成匯編程序,匯編過程把匯編程序形成二進制機器代碼,連接過程則將各個源文件生成的二進制機器代碼文件組合成一個文件。這個文件它由幾個部分組成,在程序運行時又會產(chǎn)生其他幾個部分,各個部分代表了不同的存儲區(qū)域:
--- 代碼段(Code或Text):代碼段由程序中執(zhí)行的機器代碼組成。在C語言中,程序語句進行編譯后,形成機器代碼。在執(zhí)行程序的過程中,CPU的程序計數(shù)器指向代碼段的每一條機器代碼,并由處理器依次運行。
--- 只讀數(shù)據(jù)段(RO data):只讀數(shù)據(jù)段是程序使用的一些不會被更改的數(shù)據(jù)。
--- 未初始化數(shù)據(jù)段(BSS):在程序中聲明,但是沒有初始化的變量,這些變量在程序運行之前不需要占用存儲器的空間。
--- 已初始化讀寫數(shù)據(jù)段(RW data):在程序中聲明,并且具有初值的變量,這些變量需要占用存儲器的空間,在程序執(zhí)行時它們需要位于可讀寫的內(nèi)存區(qū)域內(nèi),并具有初值,以供程序運行時讀寫。
--- 堆(heap):堆內(nèi)存只在程序運行時出現(xiàn),一般由程序員分配和釋放。
--- 棧(stack):棧內(nèi)存只在程序運行時出現(xiàn),在函數(shù)內(nèi)部使用的變量、函數(shù)的參數(shù)以及返回值將使用??臻g,棧空間由編譯器自動分配和釋放。
代碼段、只讀數(shù)據(jù)段、未初始化數(shù)據(jù)段、已初始化讀寫數(shù)據(jù)段屬于靜態(tài)區(qū)域。而堆和棧屬于動態(tài)區(qū)域。代碼段、只讀數(shù)據(jù)段、讀寫數(shù)據(jù)段將在連接之后產(chǎn)生,未初始化數(shù)據(jù)段將在程序初始化的時候開辟,堆和棧在程序的運行中分配和釋放。
-------- C語言程序分為映像和運行時兩種狀態(tài)。在編譯 - 連接后形成的映像中,將只包含代碼段、只讀數(shù)據(jù)段和讀寫數(shù)據(jù)段。在程序運行之前,將動態(tài)生成未初始化數(shù)據(jù)段(BSS),在程序的運行時將動態(tài)形成堆區(qū)域和棧區(qū)域。一般來說,在靜態(tài)的映像文件中,各個部分稱之為節(jié)(Section),而在運行時的各個部分稱之為段(Segment),如果不詳細(xì)區(qū)分,可以統(tǒng)稱為段。
---- 全局區(qū)(靜態(tài)區(qū)):全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后由系統(tǒng)釋放。
int a; 建立了存儲空間,稱為定義性聲明。
extern a; 不建立存儲空間,稱為引用性聲明。
局部變量用static聲明,則使該變量在整個程序執(zhí)行期間不釋放,分配的內(nèi)存單元始終存在。
外部變量用static聲明,則該變量只能在本文件中使用,而不能在其他文件中使用。
---- 函數(shù):本質(zhì)上是全局的,因為一個函數(shù)要被另外的函數(shù)調(diào)用但是也可以指定函數(shù)不能被其他文件調(diào)用。根據(jù)函數(shù)能否被其他文件調(diào)用,將函數(shù)分為內(nèi)部函數(shù)和外部函數(shù)。
內(nèi)部函數(shù):一個函數(shù)只能被本文件中其他函數(shù)所調(diào)用,稱為內(nèi)部函數(shù),定義時,函數(shù)類型前面加上static,即:static int fun(int a,int b); 內(nèi)部函數(shù)由稱為靜態(tài)函數(shù),作用域只限于所在文件,在不同的文件中有同名的內(nèi)部函數(shù),互不干擾。
外部函數(shù):定義時加上extern,可供其他函數(shù)調(diào)用。
4、堆和棧的比較
--- 申請方式:
???? stack -?由系統(tǒng)自動分配(局部變量)
???? heap - 需要程序員自己申請,并指明大小(malloc/new)
--- 申請后系統(tǒng)的響應(yīng)
???? stack - 只要棧的剩余空間大于所申請空間,系統(tǒng)將為程序提供內(nèi)存,否則將報異常提示棧溢出。
???? heap - 首先應(yīng)該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結(jié)點,然后將該結(jié)點從空閑結(jié)點鏈表中刪除,并將該結(jié)點的空間分配給程序。對于大多數(shù)系統(tǒng),會在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內(nèi)存空間。由于找到的堆結(jié)點的大小不一定正好等于申請的大小,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中。
--- 申請大小的限制
???? 棧 - 在Windows下,棧是向低地址擴展的數(shù)據(jù)結(jié)構(gòu)(先分配高地址,后分配低地址),是一塊連續(xù)的內(nèi)存區(qū)域。即棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在Windows下,棧的大小是2M(也有說是1M,總之是一個編譯時就確定的值),如果申請的空間超過棧的剩余空間時,將提示overflow。因此只能從棧獲得較小的空間。
???? 堆 - 堆是向高地址擴展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。
--- 申請效率的比較
???? 棧:由系統(tǒng)自動分配,速度較快。但程序員是無法控制的。
???? 堆:是由malloc/new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生碎片,不過用起來最方便。
另外,在Winodws下,最好的方式是用VirtualAlloc分配內(nèi)存,它不是在堆內(nèi),也不在棧內(nèi),是直接在進程的地址空間中保留的一塊內(nèi)存。雖然用起來最不方便,但是速度快,也最靈活。
--- 存儲內(nèi)容的比較
???? 棧:在函數(shù)調(diào)用時,第一個進棧的是主函數(shù)中的下一條指令的地址(函數(shù)調(diào)用語句的下一條可執(zhí)行語句的地址),然后是函數(shù)的各個參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令,程序由該點繼續(xù)運行。
?????堆:一般是在堆的頭部用一個字節(jié)存放堆的大小。堆中的具體內(nèi)容有程序員安排。
小結(jié):棧不夠用的情況一般是程序中分配了大量數(shù)組和遞歸函數(shù)層次太深。當(dāng)一個函數(shù)調(diào)用完成返回主函數(shù)時它會釋放該函數(shù)中所有的??臻g。
??臻g是由編譯器自動管理的,不用程序員操心。 堆是動態(tài)分配內(nèi)存的,并且你可以分配使用很大的內(nèi)存,但是用不好會產(chǎn)生內(nèi)存泄漏。并且頻繁的malloc和free會產(chǎn)生內(nèi)存碎片(有點類似磁盤碎片),因為C分配動態(tài)內(nèi)存時是尋找匹配的內(nèi)存的。而棧則不會產(chǎn)生內(nèi)存碎片,在棧上存取數(shù)據(jù)比通過指針在堆上存取數(shù)據(jù)快些。一般大家說的堆棧就是指棧(stack),而說堆時才是堆(heap)。棧是高地址向低地址生長。
5、volatile的問題
volatile的意思是易變的,一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣編譯器就不會去假設(shè)這個變量的值了。精確的說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。用volatile變量的例子:
--- 并行設(shè)備的硬件寄存器(如狀態(tài)寄存器)
--- 一個中斷服務(wù)子程序中會訪問到的非自動變量
--- 多線程應(yīng)用中被幾個任務(wù)共享的變量。
----- 一個參數(shù)既可以是const還可以是valatile的嗎?
可以的。一個例子是只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應(yīng)該試圖去修改它。
----- 一個指針可以是volatile的嗎?
可以的。盡管這并不是很常見。一個例子是當(dāng)一個中斷服務(wù)子程序修改一個指向一個buffer的指針。
6、const用途
--- 可以定義const常量
--- 可以修飾函數(shù)的參數(shù),返回值,甚至函數(shù)的定義體。被const修飾的東西都受到強制保護,可以預(yù)防意外的變動,能提高程序的健壯性。
在函數(shù)的形參前加const關(guān)鍵字意味著這個參數(shù)在函數(shù)體內(nèi)不會被修改,屬于“輸入?yún)?shù)”。
const意味著“只讀”。 const int a; 等同于 int const a; //a是一個只讀整型變量
const int *a; //a指向的整型數(shù)不可修改,即不能通過指針修改*a的值,但是a的指向是可以改變的。
int * const a; //a是常指針,指向不可以改變,指向的值可以改變