C語言存儲空間佈局以及static解析

本文我將採用Linux環境測試C語言存儲空間佈局,以及採用VC6.0來測試static的常見用法。採用linux環境來測試c語言存儲空間佈局,是因爲Linux很容易利用shell命令中的size命令查看到進程存儲區各段的大小。採用VC6.0來測試static的常見用法,是因爲我們利用VC6.0很容易創建一個工程,該工程可以包含很多源文件,這樣就很方便我們測試本文件與其他文件之間的關係了。

                  

不管是在Linux下C程序還是Windows下C程序,他們都是由正文段、數據段、BSS段、堆、棧等段構成的,只不過可能他們的各段分配地址不一樣。Linux下的C程序正文段在低地址,而Windows下的C程序的正文段(代碼段)在高地址。所有不用擔心我用Linux環境和Windows環境共同測試帶來不正確的數據。

一、C語言存儲空間佈局

C語言一直由下面部分組成:

正文段code segment/text segment,.text段):或稱代碼段,通常是用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬於只讀,某些架構也允許代碼段爲可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。CPU執行的機器指令部分。

數據段data segment,.data段):通常是用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬於靜態內存分配。

BSS段bss segment,.bss段):通常是指用來存放程序中未初始化的全局變量的一塊內存區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態內存分配。

heap):堆是用於存放進程運行中被動態分配的內存段,它的大小並不固定,可動態擴張或縮減。當進程調用malloc等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free等函數釋放內存時,被釋放的內存從堆上被剔除(堆被縮減)。

stack):棧又稱堆棧,是用戶存放程序臨時創建的局部變量,也就是我們函數大括號"{}"中定義的變量(不包括static聲明的變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,並且等調用結束後,函數的返回值也會被存放在回棧中。由於棧的先進先出特性,所有棧特別方便用來保存/恢復調用現場。從這個意義上講,把堆棧看成一個寄存、交換臨時數據的內存區。

【測試】:採用Linux環境測試

1、測試代碼如下,文件名爲progressStruct.c


保存後,輸入gcc progressSturnct.c -o progressStruct編譯生成二進制文件(可執行文件),然後輸入size progressStruct查看進程progressStruct內存各段大小


text->正文段   data->數據段,存儲已初始化全局變量段   bss->存儲未初始化全局變量段  dec->以十進制顯示總大小  hex->以十六進制顯示總大小

2、修改progressStruct.c文件,修改後代碼如下:


保存後,輸入gcc progressSturnct.c -o progressStruct編譯生成二進制文件(可執行文件),然後輸入size progressStruct 查看進程progressStruct內存各段大小


3、繼續修改progressStruct.c文件,修改後代碼如下:


保存後,輸入gcc progressSturnct.c -o progressStruct編譯生成二進制文件(可執行文件),然後輸入size progressStruct 查看進程progressStruct內存各段大小


4、3、繼續修改progressStruct.c文件,修改後代碼如下:


保存後,輸入gcc progressSturnct.c -o progressStruct編譯生成二進制文件(可執行文件),然後輸入size progressStruct 查看進程progressStruct內存各段大小


其他非主函數中的變量存儲在堆棧區


二、面向過程程序設計中的static

1、全局靜態變量

在全局變量之前加上關鍵字static修飾,全局變量就被定義成一個全局靜態變量

  1. 內存中的位置:靜態存儲器(靜態存儲區在整個程序運行期間都存在的)
  2. 初始化:未初始化的全局靜態變量會被程序自動化爲0
  3. 作用域:全局靜態變量在聲明它的文件之外是不可見,即其他文件不能使用被static修飾的變量。只能在從定義處到文件結尾中被使用。
【測試其作用域】:此測試利用VC6.0來完成
1、先測試不加static修飾全局變量,在另外一個文件來使用其他文件的全局變量
先在VC6.0上創建一個工程,命名爲StaticTest,並在這個工程中創建兩個.c文件,分別爲a.c和main.c,對應代碼如下:

運行結果如下:

2、現在修改a.c文件,修改後如下:

此時點擊變成此工程,在編譯過程沒有報錯誤,而當我們去點擊去鏈接這個工程的文件,此時就報錯了,錯誤信息如下:


總結:被static修飾的全局變量(全局靜態變量),不能被外部文件使用,只能被從定義開始到當前定義文件的結尾之間使用。

定義全局靜態變量的好處:
<1>不會被其他文件所訪問和修改。
<2>其他文件中可以使用相同名字的變量,不會發生衝突。

2、局部靜態變量

在局部變量之前加上關鍵字static,局部變量被定義成爲一個局部靜態變量
  1. 內存中的位置:靜態存儲器
  2. 初始化:未經初始化的局部變量會被程序自動初始化爲0
  3. 作用域:作用域仍爲局部作用域,當定義它的函數或語句塊結束的時候,作用域隨之結束。
注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改爲靜態存儲區。但是局部靜態變量在離開作用域之後,並沒有被銷燬,而是仍然駐留在內存當中(在其作用域外仍然可以定義相同名字的變量),直到程序結束,只不過我們不能再對他進行訪問。當static用來修飾全局變量時候,它就改變了全局變量的作用域(在聲明它的文件之外是不可見的),但是沒有改變它的存放位置,還是在靜態存儲器中。

運行結果如下:

3、靜態函數

在函數的返回類型前加上關鍵字static,函數就被定義成爲靜態函數。
函數的定義和聲明默認情況下是extern的,但是靜態函數只是在聲明它的文件中可見,不能被其他文件所用,例如:

同樣編譯沒有報錯,而在鏈接時報錯,錯誤提示找不到display()函數

定義靜態函數的好處:
<1>其他文件中可以定義相同名字的函數,不會發生衝突
<2>靜態函數不能被其他文件所用

存儲說明符auto,register,extern,static,對應兩種存儲期:自動存儲期和靜態存儲期。
auto和register對應自動存儲期。具有自動存儲期變量在進入聲明該變量的程序塊時被建立,它在該程序塊活動時存在,退出該塊時撤銷。關鍵字extern和static用來說明具有靜態存儲器的變量和函數,用static聲明的局部變量具有靜態存儲持續期(static storage duration),或靜態範圍(static extent)。雖然他的值在函數調用之間保持有效,但是其名字的可視性仍限制在其局部域內。靜態局部對象在程序執行到該對象的聲明處時被首次初始化。

由於static變量的以上特性,可實現一些特定功能。
1、統計次數功能
聲明函數的一個局部變量,並設置爲static類型,作爲一個計數器,這樣函數每次被調用的時候就可以進行級數。這是統計函數被調用次數的做好的辦法。因爲這個變量是和函數息息相關的,二函數可能在不同的地方被調用,所以從調用者的角度來統計比較困難。代碼如下:

運行結果如下:

從結果我們更加能證明:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改爲靜態存儲區。但是局部靜態變量在離開作用域之後,並沒有被銷燬,而是仍然駐留在內存當中(在其作用域外仍然可以定義相同名字的變量),直到程序結束,只不過我們不能再對他進行訪問

總結:C語言程序可以看成一系列外部對象構成,這些外部對象可能是變量,也可能是函數。而內部變量是指定義在函數內部的函數參數及變量。外部變量定義在函數之外,因此可以在許多函數中使用。由於C語言不允許在一個函數中定義其它函數,因此函數本身只能是“外部”。由於C語言代碼是以文件爲單位來組織的,一個源程序所有源文件中,一個外部變量或函數只能在某個文件中定義一次,而其他文件通過extern聲明來訪問它(定義外部變量或函數的源文件中也可以包含對該外部變量的extern聲明。)而static則可以限定變量或函數爲靜態存儲。如果用static限定外部變量與函數,則可以將該對象的作用域限定爲被編譯源文件的剩餘部分(從被定義處開始到文件末尾)。通過static限定外部對象,可以達到隱藏外部對象的目的。因而,static限定的變量或函數不會和同一程序中其他文件中同名的相沖突。如果用static限定內部變量,則該變量從程序一開始就擁有內存,不會隨其所在函數的調用和退出而分配和消失。

C語言中使用靜態函數的好處:
<1>靜態函數會被自動分配在一個一直使用的存儲器,直到退出應用程序實例,避免了調用函數時壓棧出棧,這樣速度就快得多。(常用函數可以選擇使用static修飾)
<2>關鍵字"static",譯成中文就是"靜態的",所以內部函數又稱靜態函數(相對其他文件而言,被static修飾的函數,其他文件不能訪問)。但此處"static"的含義不是指存儲方式,而指對函數的作用域僅侷限於本文件。
<3>使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自定定義的函數是否與其他文件中的函數同名。

轉載:大神作品
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章