C語言中的static 詳細分析

2puT發表於2016-07-13

          google了近三頁的關於C語言中static的內容,發現可用的資訊很少,要麼長篇大論不知所云要麼在關鍵之處幾個字略過,對於想挖掘底層原理的初學者來說參考性不是很大。所以,我這篇博文博採眾家之長,把網際網路上的資料整合歸類,並親手編寫程式驗證之。

         C語言程式碼是以檔案為單位來組織的,在一個源程式的所有原始檔中,一個外部變數(注意不是區域性變數)或者函式只能在一個源程式中定義一次,如果有重複定義的話編譯器就會報錯。伴隨著不同原始檔變數和函式之間的相互引用以及相互獨立的關係,產生了extern和static關鍵字。

        下面,詳細分析一下static關鍵字在編寫程式時有的三大類用法:

        一,static全域性變數

           我們知道,一個程式在記憶體中的佈局如圖1所示:

      其中.text段儲存程式所執行的程式二進位制檔案,.data段儲存程式所有的已初始化的全域性變數,.bss段儲存程式未初始化的全域性變數(其他段中還有很多亂七八糟的段,暫且不表)。在程式的整個生命週期中,.data段和.bss段內的資料時跟整個程式同生共死的,也就是在程式結束之後這些資料才會壽終就寢。

     當一個程式的全域性變數被宣告為static之後,它的中文名叫靜態全域性變數。靜態全域性變數和其他的全域性變數的儲存地點並沒有區別,都是在.data段(已初始化)或者.bss段(未初始化)內,但是它只在定義它的原始檔內有效,其他原始檔無法訪問它。所以,普通全域性變數穿上static外衣後,它就變成了新娘,已心有所屬,只能被定義它的原始檔(新郎)中的變數或函式訪問。

以下是一些示例程式

file1.h如下:

  1. #include <stdio.h>  
  2.   
  3. void printStr();  

我們在file1.c中定義一個靜態全域性變數hello, 供file1.c中的函式printStr訪問.

  1. #include "file1.h"  
  2.   
  3. static char* hello = "hello cobing!";  
  4.   
  5. void printStr()  
  6. {  
  7.     printf("%s\n", hello);  
  8. }  

file2.c是我們的主程式所在檔案,file2.c中如果引用hello會編譯出錯

  1. #include "file1.h"  
  2.   
  3. int main()  
  4. {  
  5.     printStr();  
  6.     printf("%s\n", hello);  
  7.     return 0;  
  8. }  

報錯如下:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file2.c: In function ‘main’:
file2.c:6: 錯誤:‘hello’ 未宣告 (在此函式內第一次使用)
file2.c:6: 錯誤:(即使在一個函式內多次出現,每個未宣告的識別符號在其
file2.c:6: 錯誤:所在的函式內只報告一次。)


如果我們將file2.c改為下面的形式:

  1. #include "file1.h"  
  2.   
  3. int main()  
  4. {  
  5.     printStr();  
  6.     return 0;  
  7. }  

則會順利編譯連線。

執行程式後的結果如下:
[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
hello cobing!

上面的例子中,file1.c中的hello就是一個靜態全域性變數,它可以被同一檔案中的printStr呼叫,但是不能被不同原始檔中的file2.c呼叫。

 

      二,static區域性變數

      普通的區域性變數在棧空間上分配,這個區域性變數所在的函式被多次呼叫時,每次呼叫這個區域性變數在棧上的位置都不一定相同。區域性變數也可以在堆上動態分配,但是記得使用完這個堆空間後要釋放之。

       static區域性變數中文名叫靜態區域性變數。它與普通的區域性變數比起來有如下幾個區別:

           1)位置:靜態區域性變數被編譯器放在全域性儲存區.data(注意:不在.bss段內,原因見3)),所以它雖然是區域性的,但是在程式的整個生命週期中存在。

           2)訪問許可權:靜態區域性變數只能被其作用域內的變數或函式訪問。也就是說雖然它會在程式的整個生命週期中存在,由於它是static的,它不能被其他的函式和原始檔訪問。

           3):靜態區域性變數如果沒有被使用者初始化,則會被編譯器自動賦值為0,以後每次呼叫靜態區域性變數的時候都用上次呼叫後的值。這個比較好理解,每次函式呼叫靜態區域性變數的時候都修改它然後離開,下次讀的時候從全域性儲存區讀出的靜態區域性變數就是上次修改後的值。
以下是一些示例程式:

     file1.h的內容和上例中的相同,file1.c的內容如下:

  1. #include "file1.h"  
  2.   
  3. void printStr()  
  4. {  
  5.     int normal = 0;  
  6.     static int stat = 0;    //this is a static local var  
  7.     printf("normal = %d ---- stat = %d\n",normal, stat);  
  8.     normal++;  
  9.     stat++;  
  10. }  

為了便於比較,我定義了兩個變數:普通區域性變數normal和靜態區域性變數stat,它們都被賦予初值0;

file2.c中呼叫file1.h:

  1. #include "file1.h"  
  2.   
  3. int main()  
  4. {  
  5.  printStr();  
  6.  printStr();  
  7.  printStr();  
  8.  printStr();  
  9.  printf("call stat in main: %d\n",stat);  
  10.  return 0;  
  11. }  

這個呼叫會報錯,因為file2.c中引用了file1.c中的靜態區域性變數stat,如下:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file2.c: In function ‘main’:
file2.c:9: 錯誤:‘stat’ 未宣告 (在此函式內第一次使用)
file2.c:9: 錯誤:(即使在一個函式內多次出現,每個未宣告的識別符號在其
file2.c:9: 錯誤:所在的函式內只報告一次。)

編譯器說stat未宣告,這是因為它看不到file1.c中的stat,下面注掉這一行:

  1. #include "file1.h"  
  2.   
  3. int main()  
  4. {  
  5.     printStr();  
  6.     printStr();  
  7.     printStr();  
  8.     printStr();  
  9. //  printf("call stat in main: %d\n",stat);  
  10.     return 0;  
  11. }  

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
normal = 0 ---- stat = 0
normal = 0 ---- stat = 1
normal = 0 ---- stat = 2
normal = 0 ---- stat = 3

執行如上所示。可以看出,函式每次被呼叫,普通區域性變數都是重新分配,而靜態區域性變數保持上次呼叫的值不變。

需要注意的是由於static區域性變數的這種特性,使得含靜態區域性變數的函式變得不可重入,即每次呼叫可能會產生不同的結果。這在多執行緒程式設計時可能會成為一種隱患。需要多加註意。


       三,static函式
              相信大家還記得C++物件導向程式設計中的private函式,私有函式只有該類的成員變數或成員函式可以訪問。在C語言中,也有“private函式”,它就是接下來要說的static函式,完成物件導向程式設計中private函式的功能。

            當你的程式中有很多個原始檔的時候,你肯定會讓某個原始檔只提供一些外界需要的介面,其他的函式可能是為了實現這些介面而編寫,這些其他的函式你可能並不希望被外界(非本原始檔)所看到,這時候就可以用static修飾這些“其他的函式”。

           所以static函式的作用域是本原始檔,把它想象為物件導向中的private函式就可以了。

下面是一些示例:

file1.h如下:

  1. #include <stdio.h>  
  2.   
  3. static int called();  
  4. void printStr();  

file1.c如下:

  1. #include "file1.h"  
  2.   
  3. static int called()  
  4. {  
  5.     return 6;  
  6. }  
  7. void printStr()  
  8. {  
  9.     int returnVal;  
  10.     returnVal = called();  
  11.     printf("returnVal=%d\n",returnVal);  
  12. }  

file2.c中呼叫file1.h中宣告的兩個函式,此處我們故意呼叫called():

  1. #include "file1.h"  
  2.   
  3. int main()  
  4. {  
  5.     int val;  
  6.     val = called();  
  7.     printStr();  
  8.     return 0;  
  9. }  

編譯時會報錯:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file1.h:3: 警告:‘called’ 使用過但從未定義
/tmp/ccyLuBZU.o: In function `main':
file2.c:(.text+0x12): undefined reference to `called'
collect2: ld 返回 1

因為引用了file1.h中的static函式,所以file2.c中提示找不到這個函式:undefined reference to 'called'

下面修改file2.c:

  1. #include "file1.h"  
  2.   
  3. int main()  
  4. {  
  5.     printStr();  
  6.     return 0;  
  7. }  

編譯執行:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
returnVal=6

       static函式可以很好地解決不同原檔案中函式同名的問題,因為一個原始檔對於其他原始檔中的static函式是不可見的。

有疏漏的地方望各位多多指教~~

相關文章