堆和棧的詳細區別對比

堆heap和棧stack的區別

堆和棧的區別我們要數據結構方向和虛擬機內存方向從兩方面去比較,兩處所說的堆棧是不同的兩種含義。

數據結構方向

堆和棧都是一種數據項按序排列的數據結構 ,都用於存放臨時數據

  1. 棧是線性結構LIFO,只允許在棧頂進行刪除(出棧)或插入(入棧)操作。
  2. 堆一般指二叉堆,可看着一棵樹。堆的根節點是最大值或者最小值,增加刪除是任意的。常用於堆排序。內存中保存new 和malloc這些自定義的內存變量。
  3. 隊列先進先出,在隊頭做刪除操作,在隊尾做插入操作。

虛擬機內存方向

一般情況下程序存放在Rom或Flash中,運行時需要拷到內存中執行,內存會分別存儲不同的信息。其內存中數據分配地址如下:

  1. 棧區(stack): 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。

  2. 堆區(heap):一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表,呵呵。

  3. 全局區(static靜態區):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。
    靜態區是一個可讀可寫的內存空間,這個空間是在你寫程序編譯好的空間地址(由編譯器決定),是固定的。- 程序結束後有系統釋放

  4. 文字常量區:常量字符串就是放在這裏的。 程序結束後由系統釋放

  5. 程序代碼區:存放函數體的二進制代碼。

創建和銷燬

1) 棧:由操作系統自動分配釋放 ,其在操作系統建立某個進程或線程時建立存儲空間,在編譯時可以指定需要的Stack的大小。
棧一般處於較高地址位,棧地址是向下增長的。在退出函數的時候,只是修改棧指針就可以把棧中的內容銷燬,所以速度最快。

棧中存放函數的參數值,局部變量的值等,都是在棧中分配空間保存,從上往下分配地址。其操作方式類似於數據結構中的棧,如int a=10,自動分配自動回收。程序通過棧的基地址和偏移量來訪問本地變量。

2)堆:是應用程序在運行的時候請求操作系統分配給自己內存,由程序員申請和釋放,所以它是動態申請的。對堆的訪問和對一般內存的訪問沒有區別。

堆區的起始地址一般處於低位,地址向上增長。若程序員不釋放,程序結束時可能由OS回收。分配方式類似於鏈表。如我們定義int[] arr=new int[10]。

Java和C/C++關於堆是有區別的

  1. 在Java中除了簡單類型(int,char等)都是在堆中分配內存,這也是程序慢的一個主要原因
  2. C/C++ 中不是自動初始化的,C/C++分別用malloc/New請求分配Heap,用free/delete銷燬內存。
  3. Java中分配Heap內存是自動初始化的。在Java中所有的對象(包括int的wrapper Integer)都是在堆中分配的。
  4. 也就說比如一個String建立對象時從兩個地方都分配內存,在Heap中分配的內存實際建立這個對象,而在Stack中分配的內存只是一個指向這個堆對象的指針(引用)而已。

緩存方式

  1. 棧使用的是一級緩存, 棧上的數據生存週期只在函數運行過程中,調用完畢立即釋放,其後不能再被訪問。
  2. 堆是存放在二級緩存中,生命週期由虛擬機的垃圾回收算法來決定(並不是一旦成爲孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。

申請響應

棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆。
在C++中堆事先是沒有在進程空間裏分配的需要手動分配,此時訪問堆空間會報一個內存訪問錯誤,而Java會自動初始化。

堆內存分配過程
1)記錄鏈表將合適的堆結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。
2)對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的 delete語句才能正確的釋放本內存空間。
3) 由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。
堆會在申請後還要做一些後續的工作,這就會引出申請效率的問題。

棧內存分配過程
在C++中當進程初始化時,系統會自動爲進程創建一個默認棧。這個棧默認所佔內存的大小根據不同系統有所不同。
進程的每個線程都有私有的“棧”,所以每個線程雖然代碼一樣,但本地變量的數據都是互不干擾。一個棧可以通過“基地址”和“棧頂”地址來描述。

棧訪問

1)有一組CPU指令可以實現對進程的內存實現堆棧訪問。其中,POP指令實現出棧操作,PUSH指令實現入棧操作。
2)CPU的ESP寄存器存放當前線程的棧頂指針,EBP寄存器中保存當前線程的棧底指針。
3)CPU的EIP寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令後,從EIP寄存器中讀取下一條指令的內存地址,然後繼續執行。

申請效率

棧:由系統自動分配,速度較快。但程序員是無法控制的。

堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。
由於從操作系統管理的內存分配所以在分配和銷燬時都要佔用時間,所以用堆的效率低的多!但是堆的好處是可以做的很大,C/C++對分配的Heap是不初始化的。

申請大小的限制

棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。棧頂的地址和棧的最大容量是系統預先規定好的,windows下的棧一般只有1-2M。如果申請的空間超過棧的剩餘空間時,將提示棧溢出。

堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

**由於棧的大小有限,所以用子函數還是有物理意義的,而不僅僅是邏輯意義。**多使用子函數和控制函數的行數,可以有效減小棧大小限制壓力。

存儲內容

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

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

堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。

總結

有人將棧比喻成去飯館喫飯,只管點才喫飯,快捷而自由度小。堆則像自己做飯菜,過程麻煩,但想喫啥做啥,自由度高。

博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收**藏 ^ _ ^ !

相關鏈接

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