【粉絲問答10】C語言關鍵字static的使用詳解

一口Linux發表於2021-03-04

視訊地址:https://www.ixigua.com/6935761378816819748

粉絲提問

粉絲問題,總結一下:
關鍵字static的使用方法。

問題

要想搞清楚關鍵字static的使用方法,必須首先搞清楚,可執行程式段的分類以及各段在記憶體區的邏輯地址的對映。

一、可執行程式記憶體分配

1. 可執行程式程式分段

一個程式的3個基本段:text段,data段,bss段

  1. BSS
    BSS(Block Started by Symbol)通常是指用來存放程式中未初始化的全域性變數和靜態變數的一塊記憶體區域。

特點是:可讀寫的,在程式執行之前BSS段會自動清0。

所以,未初始的全域性變數在程式執行之前已經成0了。

注意和資料段的區別,BSS存放的是未初始化的全域性變數和靜態變數,資料段存放的是初始化後的全域性變數和靜態變數。

UNIX下可使用size命令檢視可執行檔案的段大小資訊。如size a.out。

  1. 資料段.data
    存放在編譯階段(而非執行時)就能確定的資料,可讀可寫。

也就是通常所說的靜態儲存區,賦了初值的全域性變數和賦初值的靜態變數存放在這個區域,常量也存在這個區域。資料段,程式碼段在程式執行之前就已經確定了的。

  1. 程式碼段.text
    程式碼段通常是指用來存放程式執行程式碼的一塊記憶體區域。

這部分割槽域的大小在程式執行前就已經確定,並且記憶體區域通常屬於只讀, 某些架構也允許程式碼段為可寫,即允許自修改程式。

在程式碼段中,也有可能包含一些只讀的常數變數,例如字串常量等。

text段在編譯時確定,記憶體中被對映為只讀,但date段與bss段是可寫的。

2. c語言五大記憶體分割槽

  1. 棧區(堆疊區stack)

堆疊是由編譯器自動分配釋放,存放函式的引數和區域性變數的值(auto型別),操作方式類似於資料結構中的棧。棧的申請是由系統自動分配,如在函式內部申請一個區域性變數int h,同時判斷所申請空間是否小於棧的剩餘空間,如果小於則為其開闢空間,為程式提供記憶體,否則將報異常提示棧溢位。

  1. 堆(heap)

堆一般由程式設計師分配釋放,若程式設計師不釋放,程式結束可能由OS回收。

它與資料結構中的堆是兩回事,分配方式類似於連結串列,申請則是程式設計師自己操作使用malloc或new。

申請過程比較複雜,當系統收到程式的申請時,會遍歷記錄空閒記憶體地址的連結串列,以求尋找第一個空間大於所申請空間的堆節點,然後將該節點從空閒節點連結串列中刪除,並將該節點的空間分配給程式,有些情況下,新申請的記憶體塊的首地址記錄本次分配的記憶體塊的大小,這樣在free()時能正確的釋放記憶體空間。

  1. 全域性靜態儲存區

全域性變數與靜態變數的儲存是放在一塊的,初始化的全域性變數與靜態變數存放在一塊區域,未初始化的全域性變數與未初始化的靜態變數存放在相鄰的另一塊區域。

  1. 文字常量區

常量字串就是放在該部分,只讀儲存區,程式結束後由系統釋放

  1. 程式程式碼區

存放程式的二進位制程式碼區。

兩者之間區別是:程式碼段,資料段,堆疊段是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語言:如何定義一個和庫函式名一樣的函式,並在函式中呼叫該庫函式

參考:https://blog.csdn.net/m0_50662680/article/details/111362307
https://blog.csdn.net/thanklife/article/details/78476737

相關文章