C 中的堆與棧

2021-07-13 03:29:55 字數 4771 閱讀 8814

**:

也不知道是什麼原因,很多人總是把堆和棧混合一起,在寫程式時,總是經常脫口而出地說堆疊。網上的一些資料說堆疊的叫法是有歷史原因的,至於具體是什麼歷史原因,這不是本文所要討論的問題。

堆:在資料結構中,堆是一種滿足「堆性質」(至於什麼是堆性質可以查閱任何一本資料結構的書)的資料結構。然而,通常我們所指的堆都是指二叉堆,即一種使用陣列來模擬完全二叉樹的結構。當然,也存在其它形式的堆,包括斐波拉契堆、二項堆、楊氏表等,想獲得有關這些特殊堆的性質可以查閱演算法導論。然而,在編譯器中,堆是乙個儲存區,通常用於動態分配儲存空間,一般堆具有不連續性(在下文中將講到堆的不連續性)。

棧:在資料結構中,棧是一種按照資料項先進後出的順序排列的資料結構,我們只能在棧頂來對棧中的資料項進行操作。然而在編譯器中,棧通常是用來為函式中的臨時變數分配儲存空間,通常棧空間的分配具有連續性。

通常乙個由c++編譯的程式占用的記憶體分為以下五個部分(這些知識對理解下文至關重要,這些是對乙個基本的c++程式的儲存方式的認識):

1)棧區(stack)

是由編譯器自動分配釋放,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。

2)堆區(heap)

一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由作業系統**(如果**的不及時有可能會造成記憶體洩露)。堆空間的分配方式類似於資料結構中的鍊錶。

3)全域性區(靜態區)(static)

全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。在程式結束後由系統釋放。

4)文字常量區

用於存放常量資料,程式結束後由系統釋放。

5)程式**區

存放函式體的二進位制**。

在it面試中,通常有人會問哪個變數是堆變數,哪個變數又是棧變數,作業系統中的棧是向上(從低位址向高位址的方向)申請空間還是向下申請空間等等問題。我想只要掌握了堆和棧的區別,以及它們的工作原理,這些問題都會迎刃而解。本節將分以下幾個方面來講述它們之間的差別。

這個問題其實在第2節已經初步提到,在本小節中再次詳細說明一下,因為這對下文的理解至關重要。

主要儲存動態申請的空間。在c++中,儲存「new出來」的物件,如下程式段

int *a;

a = new int;

*a = 1;

那麼,變數a儲存的值為1,1的儲存位址在堆區,即指標a所指向的那個物件的儲存位址是在堆區,但是要注意的是指標a本身所儲存的區域是在棧區(嘿嘿,暈乎了把,可以看以下例子)。

exp01:

#include

int main ()

exp01的輸出結果如下:

指標a所指向物件的位址(堆區位址)為:0x00371100

儲存指標a本身的位址(棧區位址)為:0x0012ff7c

主要儲存程式中的臨時變數,這些臨時變數包括函式的引數變數、函式內的臨時變數、指標變數(指的是指標本身)、陣列變數等。注意:全域性變數和靜態變數不在棧區,它們是放在全域性區。

堆區的空間分配是由程式管理,而不是由系統管理。堆空間通常是由程式動態申請的。通常作業系統中有乙個記錄空閒記憶體位址的鍊錶,當系統收到程式的申請時,會遍歷該鍊錶,根據某種記憶體管理演算法,尋找乙個空間大於所申請空間的堆結點,然後將該結點從空閒結點鍊錶中刪除,並將該結點的空間分配給程式。對於這種申請方式,需要在程式中使用delete語句釋放空間,否則容易導致記憶體洩露。

堆空間的分配一般都是向高位址擴充套件,並且具有不連續性。這是由於系統是用鍊錶來管理空閒記憶體位址的,當然也就不連續了,而系統中煉表的遍歷方向通常是由低位址向高位址遍歷。我們可以通過以下例子可以看到,

exp02:

#include

int main ()

exp02的輸出結果:

0x00371100      0x00371138      0x00371170

由前文我們可以知道*a,*b,*c均為堆變數(注意指標本身為棧變數),再由輸出結果我們可以看到,a的位址小於b,b小於c,並且a,b,c之間的差不是4,而是差值較大,由此可以說明堆分配的特點是向高位址擴充套件的、不連續的。

棧通常是由系統自動分配空間的。只要系統剩餘的空間大於程式所申請的空間,那麼空間申請操作一般都會成功,否則就會出現緩衝棧溢位的錯誤。windows系統中c++編譯器的棧區空間的分配有以下一些性質: 1)

在windows

系統中,棧空間的分配是從高位址向低位址擴充套件的,並且棧空間的分配一般

具有連續性,棧頂的位址和棧的最大容量是由系統預先規定。可以通過以下例子來檢視這一性質。

exp03:

#include

int main ()

exp03的輸出結果為:

0x0012ff7c      0x0012ff78      0x0012ff74

顯然,a的位址大於b和c的位址,並且a,b,c的位址間隔均為4個位元組,這可以說明2個問題:1 棧空間的分配是由高位址向低位址擴充套件的;2 棧空間的分配一般具有連續性(即相鄰變數之間的位址是不間斷的,我做了多次實驗,均證實了這點,不過仍然不能代表正確,所以只能說一般具有連續性)。

2)c++中函式引數的空間分配

函式引數的位址分配是根據引數列表中,從左到右的方向來分配的。我們根據下面這個例子來分析:

exp04:

#include

int p(int a, int b, int *h)

int main ()

exp04的輸出結果為:

0x0012ff14      0x0012ff18      0x0012ff1c      0x0012ff08      0x0012ff04

0x0012ff14      0x0012ff18      0x0012ff1c      0x0012ff08      0x0012ff04

0x00401028      0x0012ff74

由上面的輸出結果我們可以得到以下結論:

1)進一步證實棧區的分配位址方式是由高位址向低位址擴充套件(根據主函式中變數a的位址大於指標變數h的位址);

2)函式引數變數的位址分配是由右向左的方式進行的(根據函式p中引數變數a的位址小於引數變數b的位址,引數變數b的位址小於指標引數變數h的位址,此處還發現了乙個現象就是:臨時變數c的位址比引數變數a的位址小了12個位元組,那麼編譯器需要這12個位元組是做什麼用的呢?莫非是用於儲存斷點等資訊,這些東西我們不得而知);

3)函式指標儲存在另外乙個區域(由函式指標p的位址為0x00401028,我們可以知道,它並不是儲存在一般的棧區,因為根據輸出結果,棧區的位址一般都是0x0012ffxx左右,也不是儲存在一般的堆區,因為根據輸出結果,堆區的位址一般為0x003712xx左右,那到底編譯器是如何給函式指標分配空間的呢?是另外開一塊區域嗎?這些問題我們也不得而知,不過我個人認為函式指標仍然是儲存在乙個「特殊的棧區」,這一點下文會有乙個說明);

4)函式變數申請空間的順序為:按照先引數變數,後函式內的臨時變數的順序來申請空間(由函式p中臨時變數c的位址小於引數a的位址);

5)乙個函式在呼叫結束後,所有的臨時變數都會由系統釋放,並且再次呼叫該函式時,仍然是從第一次呼叫的位址開始分配空間,這也說明了棧空間是由系統管理的,而不必程式設計師手工釋放(這一點可以由2次呼叫函式p的輸出結果一樣來說明)。

1)堆空間的**方式

堆空間通常需要使用free, delete等函式來釋放,系統本身不會對堆空間進行**。

2)棧空間的**方式

棧空間通常是在程式結束時由系統**,或者在函式呼叫完畢後,由系統自動**。

棧由系統自動分配,速度較快,但程式設計師是無法控制的。堆是由new分配的記憶體,一般速度比較慢,而且容易產生記憶體碎片。這也解釋了在寫acm程式時,使用靜態陣列要比動態陣列的速度要快。

在第3節中,我們不能確定函式指標的儲存位置,下面我們通過下面乙個例項來說明我的觀點:

exp05:

#include

int p()

int q()

int o()

void f() {}

void g() {}

void h() {}

int main ()

exp05的輸出結果為:

0x00401019

0x00401019

0x00401014

0x00401037

0x00401023

0x0040101e

0x00401032

由輸出結果,我們可以知道,函式指標的儲存不具有連續性,但也不像堆區域那樣有很大的間隔(各個函式指標的位址都相差不大)。我的觀點是:編譯器會專門開一塊連續的記憶體區域來函式指標,然後通過某種hash演算法來找到相應的函式,其空間的釋放和申請也是由系統來管理。函式指標不會像堆那樣,動態申請記憶體空間,因為我們在寫程式時,是不需要為函式指標申請空間,也不需要為函式指標釋放空間,因此這一點跟棧類似,但也它也不像棧,滿足向低位址擴充套件、位址連續等特性。雖然它不是棧,但跟棧有很多共同點,所以可以認為,函式指標是儲存在乙個「特殊的棧區」。

1)記憶體洩露

在電腦科學中,記憶體洩漏指由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況。記憶體洩漏並非指內存在物理上的消失,而是應用程式分配某段記憶體後,由於設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費。 2)

緩衝棧溢位

指程式中棧所申請的空間大於系統剩餘的空間時就會發生緩衝棧溢位的錯誤。

一點說明:寫這篇文章主要是出於學習的目的,以便以後能夠牢牢記住堆與棧的區別。這也是今天和實驗室的陳老師在討論專案中的記憶體洩露問題所談到的,由此有感而發,寫下此文,供以後參考。

鳴謝:廣州聚暉電子科技****

hint: 寫鳴謝也是學乙個同學的,因為在現實中,你完成任何事情一般都需要得到別人的幫助,在寫**的時候不也有個acknowledge嗎。

c程式設計中的堆與棧

棧與系統的設計有關,由系統來管理。不過,程式設計師可以通過如new malloc語句在堆中獲取記憶體。棧是向下生長的一塊連續的記憶體區域。棧的大小是系統設定好的。堆是向上生長,不連續的記憶體區域。因為作業系統是用鍊錶來管理記憶體的。堆的大小受系統有效虛擬記憶體的限制。棧 在函式呼叫時,第乙個進棧的是...

C 中記憶體中堆與棧的區別

1 記憶體分配方面 堆 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os 注意它與資料結構中的堆是兩回事,分配方式是類似於鍊錶。可能用到的關鍵字如下 new malloc delete free等等。棧 由編譯器 compiler 自動分配釋放,存放函式的引數值,區域性變數的值等。...

c 程式中堆與棧的區別

一 申請方式 stack 由系統自動分配。例如,宣告在函式中乙個區域性變數 int b 系統自動在棧中為b開闢空間 heap 需要程式設計師自己申請,並指明大小,在c中malloc函式 如p1 char malloc 10 在c 中用new運算子 如p2 char malloc 10 但是注意p1 ...