【粉絲問答10】C語言關鍵字static的使用詳解
影片地址://www.ixigua.com/6935761378816819748
粉絲提問
粉絲問題,總結一下:
關鍵字static的使用方法。
要想搞清楚關鍵字static的使用方法,必須首先搞清楚,可執行程式段的分類以及各段在記憶體區的邏輯地址的映射。
一、可執行程式記憶體分配
1. 可執行程式程式分段
一個程式的3個基本段:text段,data段,bss段
- BSS
BSS(Block Started by Symbol)通常是指用來存放程式中未初始化的全局變數和靜態變數的一塊記憶體區域。
特點是:可讀寫的,在程式執行之前BSS段會自動清0。
所以,未初始的全局變數在程式執行之前已經成0了。
注意和數據段的區別,BSS存放的是未初始化的全局變數和靜態變數,數據段存放的是初始化後的全局變數和靜態變數。
UNIX下可使用size命令查看可執行文件的段大小資訊。如size a.out。
- 數據段.data
存放在編譯階段(而非運行時)就能確定的數據,可讀可寫。
也就是通常所說的靜態存儲區,賦了初值的全局變數和賦初值的靜態變數存放在這個區域,常量也存在這個區域。數據段,程式碼段在程式運行之前就已經確定了的。
- 程式碼段.text
程式碼段通常是指用來存放程式執行程式碼的一塊記憶體區域。
這部分區域的大小在程式運行前就已經確定,並且記憶體區域通常屬於只讀, 某些架構也允許程式碼段為可寫,即允許自修改程式。
在程式碼段中,也有可能包含一些只讀的常數變數,例如字元串常量等。
text段在編譯時確定,記憶體中被映射為只讀,但date段與bss段是可寫的。
2. c語言五大記憶體分區
- 棧區(堆棧區stack)
堆棧是由編譯器自動分配釋放,存放函數的參數和局部變數的值(auto類型),操作方式類似於數據結構中的棧。棧的申請是由系統自動分配,如在函數內部申請一個局部變數int h,同時判斷所申請空間是否小於棧的剩餘空間,如果小於則為其開闢空間,為程式提供記憶體,否則將報異常提示棧溢出。
- 堆(heap)
堆一般由程式設計師分配釋放,若程式設計師不釋放,程式結束可能由OS回收。
它與數據結構中的堆是兩回事,分配方式類似於鏈表,申請則是程式設計師自己操作使用malloc或new。
申請過程比較複雜,當系統收到程式的申請時,會遍歷記錄空閑記憶體地址的鏈表,以求尋找第一個空間大於所申請空間的堆節點,然後將該節點從空閑節點鏈表中刪除,並將該節點的空間分配給程式,有些情況下,新申請的記憶體塊的首地址記錄本次分配的記憶體塊的大小,這樣在free()時能正確的釋放記憶體空間。
- 全局靜態存儲區
全局變數與靜態變數的存儲是放在一塊的,初始化的全局變數與靜態變數存放在一塊區域,未初始化的全局變數與未初始化的靜態變數存放在相鄰的另一塊區域。
- 文字常量區
常量字元串就是放在該部分,只讀存儲區,程式結束後由系統釋放
- 程式程式碼區
存放程式的二進位程式碼區。
兩者之間區別是:程式碼段,數據段,堆棧段是cpu級別的概念,五大分區屬於語言級別的概念,兩者是不同的概念。
3. 可執行程式記憶體空間與邏輯地址空間的映射與劃分
左邊是UNIX系統的執行文件,右邊是進程對應的邏輯地址空間的劃分情況
4. 舉例
二、static 變數
static變數主要區分靜態全局變數和全局變數、局部變數和靜態局部變數之間的區別。
1. 靜態全局變數、全局變數
靜態全局變數、全局變數的區別主要通過生存周期和作用域來區別。
全局變數 | 靜態全局變數 | |
---|---|---|
生存周期 | 程式運行到程式結束 | 程式運行開始到程式結束 |
作用域 | 所有的程式碼 | 只有當前文件可以訪問 |
程式碼段中位置 | 全局數據區 | 全局數據區 |
- a.靜態全局變數和全局變數均存放在數據段.data中;
- b. 靜態局部變數在函數內定義,生存期為整個源程式,但作用域與自動變數相同,只能在定義該變數的函數內使用。退出該函數後, 儘管該變數還繼續存在,但不能使用它。
- c. 對基本類型的靜態局部變數若在說明時未賦以初值,則系統自動賦予0值。而對自動變數不賦初值,則其值是不定的。
- d.全局變數本身就是靜態存儲方式, 靜態全局變數當然也是靜態存儲方式。
但是他們的作用域,非靜態全局 變數的作用域是整個源程式(多個源文件可以共同使用);
而靜態全局變數則限制了其作用域, 即只在定義該變數的源文件內有效, 在同一源程式的其它源文件中不能使用它。
全局變數實例
以下是b.c 和 a.c源程式碼
編譯
gcc a.c b.c
執行結果:
由編譯結果可知,文件a.c可以訪問到b.c文件中的靜態全局變數b。
靜態全局變數實例
編譯結果
由編譯結果可知,文件a.c無法訪問到b.c文件中的靜態全局變數b,所以編譯報錯。
2. 靜態局部變數、局部變數
靜態局部變數、局部變數的區別主要通過生存周期和作用域來區別。
局部變數 | 靜態局部變數 | |
---|---|---|
生存周期 | 函數調用到函數返回 | 程式運行開始到程式結束 |
作用域 | 函數內部 | 函數內部 |
程式碼段中位置 | 棧 | 全局數據區 |
靜態局部變數存放在數據段.data中,局部變數在棧中;
靜態局部變數和局部變數都只能在函數體內部才可以訪問。
函數每次訪問的靜態局部變數,該變數的值為最後一次訪問修改後的值。
舉例:
1 #include <stdio.h>
2
3
4 void func()
5 {
6 int aa = 11;
7
8 printf("aa= %d \n",aa++);
9
10 }
11
12 int main(int argc, char **argv)
13 {
14
15 func();
16 func();
17
18 return 0;
19 }
對於普通的局部變數,每次調用的時候,都會在棧里初始化1次,
1 #include <stdio.h>
2
3
4 void func()
5 {
6 static int aa = 11;
7
8 printf("aa= %d \n",aa++);
9
10 }
11
12 int main(int argc, char **argv)
13 {
14
15 func();
16 func();
17
18 return 0;
19 }
函數中靜態變數aa 只初始化一次,每次訪問的值應該是上一次調用到該函數時最後處理的結果,
三、static 函數
1. 概念:
在函數的返回類型前加上關鍵字static,函數就被定義成為靜態函數。
函數的定義和聲明默認情況下是extern的,但靜態函數只是在聲明他的文件當中可見,不能被其他文件所用。
static函數(也叫內部函數)只能被本文件中的函數調用,而不能被同一程式其它文件中的函數調用。
區別於一般的非靜態函數(外部函數) static在c裡面可以用來修飾變數,也可以用來修飾函數。
先看用來修飾變數的時候。變數在c裡面可分為存在全局數據區、棧和堆里。
其實我們平時所說的堆棧是棧而不包含堆,不要弄混。
2. 定義靜態函數的好處:
- <1>其他文件中可以定義相同名字的函數,不會發生衝突,不用擔心自己定義的函數,是否會與其它文件中的函數同名,因為同名也沒有關係。
- <2> 靜態函數不能被其他文件所用。 存儲說明符auto,register,extern,static,對應兩種存儲期:自動存儲期和靜態存儲期。
- <3> 統計次數功能
聲明函數的一個局部變數,並設為static類型,作為一個計數器,這樣函數每次被調用的時候就可以進行計數。這是統計函數被調用次數的最好的辦法,因為這個變數是和函數息息相關的,而函數可能在多個不同的地方被調用,所以從調用者的角度來統計比較困難。 - <4> 靜態函數會被自動分配在一個一直使用的存儲區,直到退出應用程式實例,避免了調用函數時壓棧出棧,速度快很多。
舉例
a.c
1 #include <stdio.h>
2
3 void func();
4
5 int main(int argc, char **argv)
6 {
7
8 func();
9
10 return 0;
11 }
b.c
1 #include <stdio.h>
2
3 int b = 10;
4
5
6 static void func()
7 {
8 printf("in func b =%d\n",b);
9 }
由編譯結果可知,a文件訪問不到b文件中的靜態函數func。
四、一個關於static變數的巧妙的用法-偷梁換柱
如何定義一個和庫函數名一樣的函數,並在函數中調用該庫函數?
關於該問題的答案,彭老師已經已經將分析過程發佈於以下文章。
粉絲提問|c語言:如何定義一個和庫函數名一樣的函數,並在函數中調用該庫函數
參考://blog.csdn.net/m0_50662680/article/details/111362307
//blog.csdn.net/thanklife/article/details/78476737