1. 程式人生 > >棧記憶體和堆記憶體的區別(一個筆試題的一部分)

棧記憶體和堆記憶體的區別(一個筆試題的一部分)

筆試題目:請解釋一個棧記憶體與一個堆記憶體的區別,請分析下面程式碼執行是否有問題,如果有問題請改正。

char* GetMemory(void)

{

     char p[] = "Hello world";

     return p;

}

void main(void)

{

     char* str = GetMemory();

     printf(str);

}

先看第一個問題:棧記憶體和堆記憶體的區別

  1. 程式的記憶體分配

             棧(stack):有編譯器自動分配和釋放,存放函式的引數、區域性變數、臨時變數、函式返回地址等;

            堆(heap):一般有程式設計師分配和釋放,如果沒有手動釋放,在程式結束時可能由作業系統自動釋放

(?這個可能針對java那樣的有回收機制的語言而說的,對於c/c++,這樣的必須要手動釋放開闢的堆記憶體),稍有不慎會引起記憶體洩漏。

      2.申請後系統的響應

棧:只要棧的剩餘空間大於所申請的空間,系統將為程式提供記憶體,否則將報異常提示棧溢位。

堆:在記錄空閒記憶體地址的連結串列中尋找一個空間大於所申請空間的堆結點,然後將該結點從空閒結點連結串列中刪除,並將該結點的空間分配給程式。另外,對於大多數系統會在這塊記憶體空間的首地址出記錄本次分配空間的大小,這樣程式碼中的delete 才能正確釋放本記憶體空間。系統會將多餘的那部分重新空閒連結串列中。

     3、申請大小限制

     棧:在Windows下,棧是向低地址擴充套件的資料結構

,是一塊連續的記憶體的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
     堆:堆是向高地址擴充套件的資料結構,是不連續的記憶體區域。這是由於系統是用連結串列來儲存的空閒記憶體地址的,自然是不連續的,而連結串列的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬記憶體。由此可見,堆獲得的空間比較靈活,也比較大。

   4、分配效率
   棧:由系統自動分配,速度較快。但程式設計師是無法控制的。
   堆:由new分配的記憶體,一般速度比較慢,而且容易產生記憶體碎片,不過用起來最方便. 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配記憶體,不是在堆,也不是在棧是直接在程序的地址空間中保留一快記憶體,雖然用起來最不方便。但是速度快,也最靈活 
   5、儲存內容

棧:在棧中,第一個進棧的是主函式下一條指令的地址,然後是函式的各個引數,在大多數編譯器中,引數是由右往左入棧,然後是函式中的區域性變數。注意,靜態變數不入棧。出棧則剛好順序相反。

堆:一般在堆的頭部用一個位元組存放堆的大小,具體內容由程式設計師安排。


根據《C++記憶體管理技術內幕》一書,在C++中,記憶體分成5個區,他們分別是堆,棧,自由存續區,全域性/靜態存續區,常量存續區。

  a) :記憶體由編譯器在需要時自動分配和釋放。通常用來儲存區域性變數和函式引數。(為執行函式而分配的區域性變數、函式引數、返回地址等存放在棧區)。棧運算分配內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。

  b) :記憶體使用new進行分配,使用delete或delete[]釋放。如果未能對記憶體進行正確的釋放,會造成記憶體洩漏。但在程式結束時,會由作業系統自動回收。

  c) 自由儲存:使用malloc進行分配,使用free進行回收。和堆類似。

  d) 全域性/靜態儲存區:全域性變數和靜態變數被分配到同一塊記憶體中,C語言中區分初始化和未初始化的,C++中不再區分了。(全域性變數、靜態資料、常量存放在全域性資料區

  e) 常量儲存區:儲存常量,不允許被修改。

  這裡,在一些資料中是這樣定義C++記憶體分配的,可程式設計記憶體在基本上分為這樣的幾大部分:靜態儲存區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。

  a)靜態儲存區:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。它主要存放靜態資料、全域性資料和常量

  b)棧區:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。

  c)堆區:亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意大小的記憶體,程式設計師自己負責在適當的時候用free或 delete釋放記憶體。動態記憶體的生存期可以由我們決定,如果我們不釋放記憶體,程式將在最後才釋放掉動態記憶體。 但是,良好的程式設計習慣是:如果某動態記憶體不再使用,需要將其釋放掉,否則,我們認為發生了記憶體洩漏現象。

\

  圖3 典型c++記憶體區域

  總結:C++與C語言的記憶體分配存在一些不同,但是整體上就一致的,不會影響程式分析。就C++而言,不管是5部分還是3大部分,只是分法不一致,將5部分中的c)d)e)合在一起則是3部分的a)。


下面幾段程式碼,則會讓你有豁然明白的感覺:

void fn()

{

int* p = new int[5];

}

  看到new,首先應該想到,我們分配了一塊堆記憶體,那麼指標p呢? 它分配的是一塊棧記憶體,所以這句話的意思就是:棧記憶體中存放了一個指向一塊堆記憶體的指標p程式會先確定在堆中分配記憶體的大小,然後呼叫 operator new分配記憶體,然後返回這塊記憶體的首地址,放入棧中

  注意:這裡為了簡單並沒有釋放記憶體,那麼該怎麼去釋放呢? 是deletep麼? NO,錯了,應該是delete [ ] p,這是告訴編譯器:刪除的是一個數組


//main.cpp
int a = 0; 全域性初始化區
char *p1; 全域性未初始化區
main()
{
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456\0在常量區,p3在棧上。
static int c =0; 全域性(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20位元組的區域就在堆區。
strcpy(p1, "123456"); 123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。
}

參考:

1、 http://www.cnblogs.com/heyonggang/p/3250220.html

2、關於棧、堆、靜態儲存區最大可分配大小的探討 http://www.kankanews.com/ICkengine/archives/9267.shtml

4、堆記憶體和棧記憶體詳解 http://qq164587043.blog.51cto.com/261469/49492