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