天天看點

C語言堆棧入門——堆和棧的差別【頂嵌原創】

 在計算機領域,堆棧是一個不容忽視的概念,我們編寫的C語言程式基本上都要用到。但對于很多的初學着來說,堆棧是一個很模糊的概念。堆棧:一種資料結構、一個在程式運作時用于存放的地方,這可能是很多初學者的認識,因為我曾經就是這麼想的和彙編語言中的堆棧一詞混為一談。我身邊的一些程式設計的朋友以及在網上看帖遇到的朋友中有好多也說不清堆棧,是以我想有必要給大家分享一下我對堆棧的看法,有說的不對的地方請朋友們不吝賜教,這對于大家學習會有很大幫助。

    首先在資料結構上要知道堆棧,盡管我們這麼稱呼它,但實際上堆棧是兩種資料結構:堆和棧。

    堆和棧都是一種資料項按序排列的資料結構。

    我們先從大家比較熟悉的棧說起吧,它是一種具有後進先出性質的資料結構,也就是說後存放的先取,先存放的後取。這就如同我們要取出放在箱子裡面底下的東西(放入的比較早的物體),我們首先要移開壓在它上面的物體(放入的比較晚的物體)。而堆就不同了,堆是一種經過排序的樹形資料結構,每個結點都有一個值。通常我們所說的堆的資料結構,是指二叉堆。堆的特點是根結點的值最小(或最大),且根結點的兩個子樹也是一個堆。由于堆的這個特性,常用來實作優先隊列,堆的存取是随意,這就如同我們在圖書館的書架上取書,雖然書的擺放是有順序的,但是我們想取任意一本時不必像棧一樣,先取出前面所有的書,書架這種機制不同于箱子,我們可以直接取出我們想要的書。

    然而我要說的重點并不在這,我要說的堆和棧并不是資料結構的堆和棧,之是以要說資料結構的堆和棧是為了和後面我要說的堆區和棧區差別開來,請大家一定要注意。

    下面就說說C語言程式記憶體配置設定中的堆和棧,這裡有必要把記憶體配置設定也提一下,大家不要嫌我啰嗦,一般情況下程式存放在Rom或Flash中,運作時需要拷到記憶體中執行,記憶體會分别存儲不同的資訊,如下圖所示:

    記憶體中的棧區處于相對較高的位址以位址的增長方向為上的話,棧位址是向下增長的,棧中配置設定局部變量空間,堆區是向上增長的用于配置設定程式員申請的記憶體空間。另外還有靜态區是配置設定靜态變量,全局變量空間的;隻讀區是配置設定常量和程式代碼空間的;以及其他一些分區。

來看一個網上很流行的經典例子:

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);  堆

  }

  

    不知道你是否有點明白了,堆和棧的第一個差別就是申請方式不同:棧(英文名稱是stack)是系統自動配置設定空間的,例如我們定義一個 char a;系統會自動在棧上為其開辟空間。而堆(英文名稱是heap)則是程式員根據需要自己申請的空間,例如malloc(10);開辟十個位元組的空間。由于棧上的空間是自動配置設定自動回收的,是以棧上的資料的生存周期隻是在函數的運作過程中,運作後就釋放掉,不可以再通路。而堆上的資料隻要程式員不釋放空間,就一直可以通路到,不過缺點是一旦忘記釋放會造成記憶體洩露。還有其他的一些差別我認為網上的朋友總結的不錯這裡轉述一下:

1.申請後系統的響應

棧:隻要棧的剩餘空間大于所申請空間,系統将為程式提供記憶體,否則将報異常提示棧溢出。

堆:首先應該知道作業系統有一個記錄空閑記憶體位址的連結清單,當系統收到程式的申請時,會周遊該連結清單,尋找第一個空間大于所申請空間的堆。

    結點,然後将該結點從空閑結點連結清單中删除,并将該結點的空間配置設定給程式,另外,對于大多數系統,會在這塊記憶體空間中的首位址處記錄本次配置設定的大小,這樣,代碼中的 delete語句才能正确的釋放本記憶體空間。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的将多餘的那部分重新放入空閑連結清單中。

    也就是說堆會在申請後還要做一些後續的工作這就會引出申請效率的問題。

2.申請效率的比較

棧:由系統自動配置設定,速度較快。但程式員是無法控制的。

堆:是由new配置設定的記憶體,一般速度比較慢,而且容易産生記憶體碎片,不過用起來最友善。

3.申請大小的限制

棧:在Windows下,棧是向低位址擴充的資料結構,是一塊連續的記憶體的區域。這句話的意思是棧頂的位址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就确定的常數),如果申請的空間超過棧的剩餘空間時,将提示overflow。是以,能從棧獲得的空間較小。

堆:堆是向高位址擴充的資料結構,是不連續的記憶體區域。這是由于系統是用連結清單來存儲的空閑記憶體位址的,自然是不連續的,而連結清單的周遊方向是由低位址向高位址。堆的大小受限于計算機系統中有效的虛拟記憶體。由此可見,堆獲得的空間比較靈活,也比較大。

4.堆和棧中的存儲内容

棧: 在函數調用時,第一個進棧的是主函數中函數調用後的下一條指令(函數調用語句的下一條可執行語句)的位址,然後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然後是函數中的局部變量。注意靜态變量是不入棧的。

    當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的位址,也就是主函數中的下一條指令,程式由該點繼續運作。

堆:一般是在堆的頭部用一個位元組存放堆的大小。堆中的具體内容有程式員安排。

5.存取效率的比較

  char s1[] = "aaaaaaaaaaaaaaa";

  char *s2 = "bbbbbbbbbbbbbbbbb";

  aaaaaaaaaaa是在運作時刻指派的;

  而bbbbbbbbbbb是在編譯時就确定的;

  但是,在以後的存取中,在棧上的數組比指針所指向的字元串(例如堆)快。

  比如:

  #include

  void main()

  char a = 1;

  char c[] = "1234567890";

  char *p ="1234567890";

  a = c[1];

  a = p[1];

  return;

  對應的彙編代碼

  10: a = c[1];

  00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

  0040106A 88 4D FC mov byte ptr [ebp-4],cl

  11: a = p[1];

  0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

  00401070 8A 42 01 mov al,byte ptr [edx+1]

  00401073 88 45 FC mov byte ptr [ebp-4],al

堆和棧的差別可以引用一位前輩的比喻來看出:

    使用棧就象我們去飯館裡吃飯,隻管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。

    使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。比喻很形象,說的很通俗易懂,不知道你是否有點收獲。

文章出處:http://www.top-e.org/jiaoshi/html/427.html

繼續閱讀