1. 程式人生 > >C/C++中的變數儲存類別以及記憶體分配

C/C++中的變數儲存類別以及記憶體分配

本文也是總結了網上一些大神們的部落格(寫得太牛逼了),然後結合自己看書的一些內容。參考:

                  http://hi.baidu.com/lcplj123/item/e984cd0f921e39c12e4c6baa

http://www.eefocus.com/zhangjingbin/blog/10-05/190629_014f8.html

1.變數的儲存類別

        從變數值存在的角度來分,可以分為靜態儲存方式和動態儲存方式。所謂靜態儲存方式指在程式執行期間由系統分配固定的儲存空間的方式(程式開始執行時分配,在程式完畢時釋放,在程式過程中它們佔據國定的儲存單元,而不是動態分配和釋放)。而動態儲存方式在執行期間根據需要進行動態儲存方式(在程式過程中申請和釋放的一些空間

)。記憶體中供使用者使用的儲存空間分為:程式區、靜態儲存區和動態儲存區。程式放在程式區,資料放在靜態儲存區和動態儲存區。在動態儲存區存放函式形式引數、自動變數(未加static宣告的區域性變數)和函式呼叫時的現場保護和返回地址。

在C語言中每一個變數和函式兩個屬性:資料型別和資料的儲存類別。儲存方式分為兩大類:靜態儲存和動態儲存。具體保護:自動的(auto)、靜態的(static)、暫存器的(register)、外部的(extern)。下面分別對上面關鍵字進行說明。

      (1)auto:這個關鍵字是預設的,這個關鍵字屬於動態儲存區,宣告時不進行預設初始化。

      (2) static:如果希望區域性變數

呼叫結束後不消失保留此值(即加上改關鍵字在靜態區分配空間儲存),有預設初值。靜態區域性變數在編譯時賦初值(即賦初值一次),以後每次執行保留上次的結果。如果是全域性變數加上static時,就表示該變數的作用域只限於本模組。(全域性變數預設儲存在靜態儲存區)。同樣,在多檔案中為了防止名字衝突也可以將函式宣告為static,這樣只在檔案內部引用

(3)register:一般情況下變數時存在記憶體中,程式用到變數時發出指令將記憶體中的該變數送到運算器中,經過運算,再儲存到內種。這樣如果頻繁的使用某變數就將其定義為register型別(自由區域性自動變數和函式形式引數),直接放在CPU中,提高效率。(一般的編譯器為使用者考慮這個問題,一般不需要考慮)

      (4)extern:用來宣告外部變數和外部函式(和static一種用法相對),extern只是宣告而不是定義。兩種情況:同一檔案中宣告extern(表示變數定義在當前引用的後面);多檔案中宣告外部變數(表示該變數定義在檔案外部)。函式宣告也是如此(一般情況函式宣告都沒有extern,省略的)。

注:類具有封裝性,所以類中的成員均不能使用關鍵字extern、auto或register限定其儲存型別。

2.C語言記憶體分配機制

 (1)棧(Stack):位於函式內的區域性變數(包括函式實參),由編譯器負責分配釋放,函式結束,棧變數失效。

    (2)堆(Heap):由程式設計師用malloc/calloc/realloc分配,free釋放。如果程式設計師忘記free了,則會造成記憶體洩露,程式結束時該片記憶體會由OS回收,但程式只要不結束,就有可能造成記憶體洩露。(程式設計師負責分配和釋放)

    (3)全域性區/靜態區(Global Static Area): 全域性變數和靜態變數存放區,程式一經編譯好,該區域便存在。並且在C語言中初始化的全域性變數和靜態變數和未初始化的放在相鄰的兩個區域(在C++中,由於全域性變數和靜態變數編譯器會給這些變數自動初始化賦值,所以沒有區分了)。由於全域性變數一直佔據記憶體空間且不易維護,推薦少用。程式結束時釋放。

    (4)C風格字串常量儲存區: 專門存放字串常量的地方,程式結束時釋放。

    (5)程式程式碼區:存放程式二進位制程式碼的區域。

例項1:

int a = 0; //全域性初始化區 

char *p1; //全域性未初始化區(C++中則初始化為NULL)

int main() 

   int b;                  //b分配在棧上,整型 

   char s[] = "abc";      //s分配在棧上,char *型別;"abc\0"分配在棧上,執行時賦值,函式結束銷燬

    char *p2;               //p2分配在棧上,未初始化 

   char *p3 = "123456";   //p3指向"123456"分配在字串常量儲存區的地址,編譯時確定  p3儲存在棧上

   static int c = 0;       //c在全域性(靜態)初始化區,可以多次跨函式呼叫而保持原值 

   p1 = (char *)malloc(10); //p1在全域性未初始化區,指向分配得來得10位元組的堆區地址 

   p2 = (char *)malloc(20); //p2指向分配得來得20位元組的堆區地址

   strcpy(p1, "123456");   //"123456"放在字串常量儲存區,編譯器可能會將它與p3所指向的"123456"優化成一塊

   return 0;

}


3.C++語言記憶體分配機制

在C++語言中,與C類似,不過也有所不同,記憶體主要分為如下5個儲存區:

    (1)棧(Stack):位於函式內的區域性變數(包括函式實參),由編譯器負責分配釋放,函式結束,棧變數失效。

    (2)堆(Heap):這裡與C不同的是,該堆是由new申請的記憶體,由delete或delete[]負責釋放。

    (3)自由儲存區(Free Storage):由程式設計師用malloc/calloc/realloc分配,free釋放。如果程式設計師忘記free了,則會造成記憶體洩露,程式結束時該片記憶體會由OS回收。

    (4)全域性區/靜態區(Global Static Area): 全域性變數和靜態變數存放區,程式一經編譯好,該區域便存在。在C++中,由於全域性變數和靜態變數編譯器會給這些變數自動初始化賦值,所以沒有區分了初始化變數和未初始化變量了。需要說明一點,全域性靜態變數和區域性靜態變數都是儲存在同一個靜態區(全域性區),只是作用域不同。

    (5)常量儲存區: 這是一塊比較特殊的儲存區,專門儲存不能修改的常量(一般是const修飾的變數,或是一些常量字串)。

4.堆與棧的區別

(1)棧

    具體的講,現代計算機(馮諾依曼序列執行機制),都直接在程式碼低層支援棧的資料結構。這體現在,有專門的暫存器指向棧所在的地址(SS,堆疊段暫存器,存放堆疊段地址);有專門的機器指令完成資料入棧出棧的操作(彙編中有PUSH和POP指令)。

      這種機制的特點是效率高,但支援資料的資料有限,一般是整數、指標、浮點數等系統直接支援的資料型別,並不直接支援其他的資料結構(可以自定義棧結構支援多種資料型別)。因為棧的這種特點,對棧的使用在程式中非常頻繁的 。對子程式的呼叫就是直接利用棧完成的。機器的call指令裡隱含了把返回地址入棧,然後跳轉至子程式地址的操作,而子程式的ret指令則隱含從堆疊中彈出返回地址並跳轉之的操作。

      C/C++中的函式自動變數就是直接使用棧的例子,這也就是為什麼當函式返回時,該函式的自動變數自動失效的原因,因而要避免返回棧記憶體和棧引用,以免記憶體洩露。

(2)堆

和棧不同的是,堆得資料結構並不是由系統(無論是機器硬體系統還是作業系統)支援的,而是由函式庫提供的。基本malloc/calloc/realloc/free函式維護了一套內部的堆資料結構(在C++中則增加了new/delete維護)。

當程式用這些函式去獲得新的記憶體空間時,這套函式首先試圖從內部堆中尋找可用的記憶體空間(常見記憶體分配演算法有:首次適應演算法、迴圈首次適應演算法、最佳適應演算法和最差適應演算法等)。如果沒有可用的記憶體空間,則試圖利用系統呼叫來動態增加程式資料段的記憶體大小,新分配得到的空間首先被組織進內部堆中去,然後再以適當的形式返回給呼叫者。當程式釋放分配的記憶體空間時,這片記憶體空間被返回到內部堆結構中,可能會被適當的處理(比如空閒空間合併成更大的空閒空間),以更適合下一次記憶體分配申請。

        這套複雜的分配機制實際上相當於一個記憶體分配的緩衝池(Cache),使用這套機制有如下原因:系統呼叫可能不支援任意大小的記憶體分配。有些系統的系統呼叫只支援固定大小及其倍數的記憶體請求(按頁分配),這樣的話對於大量的小記憶體分配來說會造成浪費;系統呼叫申請記憶體可能是代價昂貴的。 系統呼叫可能涉及到使用者態和核心態的轉換;沒有管理的記憶體分配在大量複雜記憶體的分配釋放操作下很容易造成記憶體碎片。

(3)棧和堆的對別

從以上介紹中,它們有如下區別:

       a、棧是系統提供的功能,特點是快速高效,缺點是由限制,資料不靈活;而堆是函式庫提供的功能,特點是靈活方便,資料適應面廣,但是效率有一定降低。

       b、棧是系統資料結構,對於程序/執行緒是唯一的;堆是函式庫內部資料結構,不一定唯一。不同堆分配的記憶體無法互相操作。

       c、棧空間分靜態分配和動態分配,一般由編譯器完成靜態分配,自動釋放,棧的動態分配是不被鼓勵的;堆得分配總是動態的,雖然程式結束時所有的資料空間都會被釋放回系統,但是精確的申請記憶體/釋放記憶體匹配是良好程式的基本要素。

       d、碎片問題:對於堆來講,頻繁的new/delete等操作勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式的效率降低;對於棧來講,則不會存在這個問題,因為棧是後進先出(LIFO)的佇列。

       e、生長方向:堆的生長方向是向上的,也就是向這記憶體地址增加的方向;對於棧來講,生長方向卻是向下的,是向著記憶體地址減少的方向增長。

       f、分配方式:堆都是動態分配的,沒有靜態分配的堆;棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配則由alloca函式進行分配,但是棧的動態分配和堆不同,它的動態分配是由編譯器進行釋放,無需我們手工實現。

       g、分配效率:棧是機器系統提供的資料結構,計算機在底層提供支援,分配有專門的堆疊段暫存器,入棧出棧有專門的機器指令,這些都決定了棧的高效率執行。而堆是由C/C++函式庫提供的,機制比較複雜,有不同的分配演算法,易產生記憶體碎片,需要對記憶體進行各種管理,效率比棧要低很多。


例項2:

看下面的一小段程式碼,體會堆與棧的區別:

int foo()

{

   //其餘程式碼  

   int *p = new int[5];

   //其餘程式碼

   return 0;

}

其中的語句int *p = new int[5];就包含了堆與棧。其中new關鍵字分配了一塊堆記憶體,而指標p本身所佔得記憶體為棧記憶體(一般4個位元組表示地址)。這句話的意思是在棧記憶體中存放了一個指向一塊堆記憶體的指標p。在程式中先確定在堆中分配記憶體的大小,然後呼叫new關鍵字分配記憶體,最後返回這塊記憶體首址,放入棧中。這段程式碼在VC6下的彙編程式碼為:

00401028   push         14h

0040102A   call         operator new(00401060)

0040102F   add          esp,4

00401032   mov          dword ptr [ebp-8],eax

00401035   mov          eax,dword ptr [ebp-8]

00401038   mov          dword ptr [ebp-4],eax

如果需要釋放記憶體,這裡我們需要使用delete[] p,告訴編譯器,我要刪除的是一個數組。

5.一個非常經典的例子

例項3:

看下面的一小段程式碼,試著找出其中的錯誤:

#include<iostream>

using namespace std;

int main()

{

   chara[] ="hello";

   a[0]= 'X';

   cout<< a<< endl;

   char*p ="world";

   p[0]= 'X';

   cout<< p<< endl;

   return0;

}

       發現問題了嗎?是的,字元陣列a的容量是6個字元,其內容為"hello\0"。a的內容時可以改變的,比如a[0]='X',因為其是在棧上分配的,也就是在執行時確定的內容。但是指標p指向的字串"world"分配在字串常量儲存區,內容為"world\0",常量字串的內容時不可以修改的。從語法上來說,編譯器並不覺得語句p[0]='X'有什麼問題,但是在執行時則會出現"accessviolation"非法記憶體訪問的問題。


[html] view plaincopyprint?
  1. <PREstyle="BACKGROUND-COLOR: rgb(255,255,255)"class=htmlname="code"sizcache="1"sizset="2"><PREstyle="BACKGROUND-COLOR: rgb(255,255,255)"class=htmlname="code"sizcache="1"sizset="3"><PRE></PRE>
  2. <PRE></PRE>
  3. <PRE></PRE>
  4. <PRE></PRE>
  5. <PRE></PRE>
  6. <PRE></PRE>
  7. <PRE></PRE>
  8. <PRE></PRE>
  9. <PRE></PRE>
  10. <PRE></PRE>
  11. <PRE></PRE>
  12. <PRE></PRE>
  13. <PRE></PRE>
  14. <PRE></PRE>
  15. <PRE></PRE>
  16. <PRE></PRE>
  17. <PRE></PRE>
  18. <PRE></PRE>
  19. <PRE></PRE>
  20. <PRE></PRE>
  21. <PRE></PRE>
  22. <PRE></PRE>
  23. <PRE></PRE>
  24. <PRE></PRE>
  25. <PRE></PRE>
  26. <PRE></PRE>
  27. <PRE></PRE>
  28. <PRE></PRE>
  29. <PRE></PRE>
  30. <PRE></PRE>
  31. <PRE></PRE>
  32. <PRE></PRE>
  33. </PRE></PRE>

相關推薦

C/C++變數儲存類別以及記憶體分配

本文也是總結了網上一些大神們的部落格(寫得太牛逼了),然後結合自己看書的一些內容。參考:                   http://hi.baidu.com/lcplj123/item/e984cd0f921e39c12e4c6baa http://www.eefo

C語言變數儲存型別有哪些?

    變數的儲存型別是C語言的重要組成部分,也是C語言學習的基礎。那C語言中變數的儲存型別有哪幾種呢?在程式設計過程中又該怎樣運用呢?就這一問題,我今天給大家介紹一下!總的來說,C語言中變數的儲存型別可以分為四種,他們分別是自動變數、靜態變數、外部變數和暫存器變數這四種,

c語言較常見的由記憶體分配引起的錯誤_記憶體越界_記憶體未初始化_記憶體太小_結構體隱含指標

1.指標沒有指向一塊合法的記憶體   定義了指標變數,但是沒有為指標分配記憶體,即指標沒有指向一塊合法的內淺顯的例子就不舉了,這裡舉幾個比較隱蔽的例子。 1.1結構體成員指標未初始化 1 2 3 4 5 6 7

C語言程式碼和各種常量、變數記憶體儲存位置及記憶體優化

全域性變數、靜態區域性變數儲存在全域性資料區,初始化的和未初始化的分別儲存在一起; 普通區域性變數儲存在堆疊中; 全域性變數和區域性變數在記憶體裡的區別? 一、預備知識—程式的記憶體分配  一個由c/C++編譯的程式佔用的記憶體分為以下幾個部分  1、棧區(stack

變數宣告和定義的區別||變數儲存類別小結(C程式設計的內容)

我們在程式設計中,時時刻刻都用到變數的定義和變數的宣告,可有些時候我們對這個概念不是很清楚,知道它是怎麼用,但卻不知是怎麼一會事,下面我就簡單的把他們的區別介紹如下: 變數的宣告有兩種情況:

2、C語言變數儲存,生存週期,作用範圍分類

C語言中變數的儲存類別一:按作用域劃分1、區域性變數:自動區域性變數、靜態區域性變數、暫存器變數。(區域性可見性)2、全域性變數:靜態外部變數、外部變數。(全域性可見性)二:按生存周期劃分1、動態儲存:自動變數、暫存器變數、形式引數。(生存時間只限於其作用範圍)2、靜態儲存:靜態區域性變數、靜態外部變數、外

C++變數儲存型別

儲存類 auto 儲存類 auto 儲存類是所有區域性變數預設的儲存類。 {int mount;autoint month;} 上面的例項定義了兩個帶有相同儲存類的變數,auto 只能用在函式

C語言變數的作用域與儲存型別

1.      全域性變數與區域性變數 任何函式或變數均只能定義一次,可宣告多次。定義在程式碼塊之內(函式塊內)的變數為區域性變數,定義在函式外部的變數為全域性變數,作用範圍是整個專案(不單單是本檔案),因此也不能在其它檔案中再次定義同名變數,函式也是如此。 2.     

C語言存儲類別又分為四類:自動(auto)、靜態(static)、寄存器的(register)和外部的(extern)。

字符變量 修飾 例如 register ext 進行 適合 sta -- 除法運算中註意: 如果相除的兩個數都是整數的話,則結果也為整數,小數部分省略,如8/3 = 2;而兩數中有一個為小數,結果則為小數,如:9.0/2 = 4.500000。 取余運算中註意: 該運算只適

C語言變數名及函式名的命名規則與駝峰命名法

一、C語言變數名的命名規則:(可以字母,數字,下劃線混合使用) 1. 只能以字母或下劃線開始;2. 不能以數字開始;3. 一般小寫;4. 關鍵字不允許用(eg:int float=2//error  float 為保留字不允許用); 二、函式名的命名規則 1.見名知意;2.自定義函式函式名首字

關於C語言變數作用域的個人心得

    這是本人的第一篇部落格,內容簡單總結淺陋。但這會是我寫部落格的開始,好啦!廢話不多說。。。。     學過C語言的同學可能都知道,在C中變數都具有作用域的說法。以下是標準的解釋和案例:    &

C語言變數的大小

基本資料型別大小(與機器有關,需要測試): type 位元組 int 4 short 2 long 4 float 4 double 8 long double 12

C++ 程式碼和變數儲存位置

棧:程式自動分配,編譯器在需要的時候分配,不需要的時候自動清除的變數儲存區 堆:程式設計師手動分配由new分配的記憶體,需要自己在程式裡面手動釋放 常量儲存區:存放的是常量,不允許修改,編譯時分配,程

C語言變數和函式的宣告與定義

一、變數在將變數前,先解釋一下宣告和定義這兩個概念。宣告一個變數意味著向編譯器描述變數的型別,但並不為變數分配儲存空間。定義一個變數意味著在宣告變數的同時還要為變數分配儲存空間。在定義一個變數的同時還可以對變數進行初始化。 區域性變數通常只定義不宣告,而全域性變數多在原始檔中定義,在標頭檔案中宣告。 區域性變

C語言外部變數的使用以及erxtern的用法

網上有很多帖子問C語言中extern的用法,而且回答的詳細程度各盡不同. 所以我就像寫一篇博文來談談我對extern的看法,不一定十分恰當,只當大家共勉. 變數定義性宣告和引用性宣告 變數的宣告有兩種情況: 1、一種是需要建立儲存空間的。 例如:

C語言變數的宣告與分號的使用

斷斷續續學習C語言多年了,可能自學這種語言, 對我來說難度有點大... 1.變數和函式使用前需要宣告, 而宣告從執行結果上分為兩種      A.宣告並建立變數  (定義性宣告, 分配記憶體)      B.只宣告變數 (引用性宣告)      A例:int age

模擬可變分割槽儲存管理的記憶體分配C

要求: 系統根據申請者的要求,按照一定的分配策略分析記憶體空間的使用情況,找出能滿足請求的空閒區,分給申請者;當程式執行完畢或主動歸還記憶體資源時,系統要收回它所佔用的記憶體空間或它歸還的部分記憶體空間,主存分配演算法使用最壞適應分配演算法。 程式執行時根據檔案內容初始化

C語言變數的宣告和定義

變數宣告和變數定義變數定義:用於為變數分配儲存空間,還可為變數指定初始值。程式中,變數有且僅有一個定義。變數宣告:用於向程式表明變數的型別和名字。定義也是宣告,extern宣告不是定義定義也是宣告:當定義變數時我們聲明瞭它的型別和名字。extern宣告不是定義:通過使用ext

C/C++語言變數作用域:區域性變數,全域性變數,檔案級變數

C/C++語言中的變數分為全域性變數和區域性變數。這種劃分方式的依據是變數的可見範圍或者叫做作用域。 1 區域性變數 區域性變數指的是定義在{}中的變數,其作用域也在這個範圍內。雖然常見的區域性變數都是定義在函式體內的,也完全可以人為的增加一對大括號來限定變

C語言變數未賦值問題總結

正是如此一般來說 全域性變數、靜態變數處於資料區,預設初始化為0 (如果指定初始值,則為指定的值)而區域性變數處於堆疊區,其數值是隨機的,即當時記憶體中的值。總體來說不要利用編譯器的這種預設原則而是應當顯示的指定必要的初始化不是笨拙而是規範