為什麼c 中要分為heap(堆)和stack(棧)

時間 2021-05-06 09:36:05

1樓:吃草的企鵝

首先,其實不是c++特有的,只是c/c++把記憶體管理的細節暴露出來了。

其實,要講清楚兩者的不同就要從函式是如何呼叫和返回說起。在函式呼叫的時候,程式跳到函式在虛擬記憶體中的開始位址,從而為了在執行完成後回到呼叫位置的位址執行,需要把呼叫位置位址記錄下來,這個位址就儲存在棧裡面。實際上,在跳到函式開始的位址前,呼叫者要負責把返回位址和原來的棧底位址壓棧,然後跳到函式的開始的位址執行。

一般函式的開始的位址執行的指令是分配棧空間的指令(通過棧頂位址減小方式),也就是說乙個函式的棧能夠使用的空間是編譯後就已經確定了,它是不能夠在執行時動態分配的。而堆空間是通過堆塊來組織的,它不是乙個線性空間,就不存在「底」和「頂」(當然實際空間還是有限的)。他在使用的時候,從空閒的堆塊中選出合適大小的堆塊,從中挖出所申請的大小的空間分配給使用者,它是在執行時動態地執行這樣的分配空間的行為。

其實你能夠看出來,棧不僅僅用於儲存你在程式裡編寫的變數,還儲存了控制程式執行流的資料。而且由於在乙個函式內部有棧底和棧頂,所以棧的空間大小是在編譯的時候就確定。而堆用於儲存在執行時才能決定大小的資料,而且和程式的執行流無關。

2樓:猿某人

你學了這麼久,先不說棧空間有限,就從沒遇到跨域生存的內容?

比如乙個影象類,有負責空間建立和銷毀的函式,有處理每個影象元素的函式。

影象占用的空間怎麼放到棧裡?

如果用棧分配,函式退出空間位址就無效了,就沒了,難道要從main開始套娃嗎?

3樓:kevin

靠前的幾個答案,似乎只回答了堆和棧怎麼使用。c++區分堆和棧的主要原因,是主流cpu就是採用這種方案:

大部分cpu都會有sp暫存器用來表示棧頂位置。然後提供一組指令來操作棧,比如:push pop等

如果語言也提供了棧操作,這些操作就可以直接轉換成硬體棧操作,以提高執行效率。

4樓:沖壓件

我第一次見到heap是在win31程式設計手冊上,當時很吃驚。mem,stack,register容易理解,heap是什麼,後來慢慢的有些明白,31不是真正的多工系統,任務切換時引數頻繁彈出壓入棧,而當時機器記憶體都比較小,需要硬碟開快取,甚至建立覆蓋檔案,這時如果程式分配的內存在機器記憶體中非規則分布,切換時消耗太大,因此在記憶體中預先劃乙個或幾個規則塊,然後分配給任務使用,切換起就方便多了,我是這麼理解的。我對新版Cplus不懂,但在舊版中沒有heap這個關鍵字。

heap應該是儲存管理關心的物件,這應該是作業系統的組成,至少應該是c呼叫API實現的。應該和語言無關。

5樓:汪淘

先糾正乙個提問中的錯誤,不是c或者c++中引入了堆和棧的概念的,在更早的彙編年代就有棧的概念了。

在計算機語言引入function的概念時,為了記下call的back路徑,必須同時引入乙個後進先出的資料結構,於是就有了棧。然後這種棧結構天然很適合儲存資料,所以不分時間前後的幾乎同一時間,又引入了push和pop兩個概念用來主動的出入棧。於是到了現在我們看到的高階語言裡引數的一部分傳參,區域性變數,暫存器的儲存現場等內容都在棧上實現。

但棧也有它很大的侷限,最討厭的是它和函式呼叫相關,並且遵循嚴格的出入棧順序,必須連續使用。這些限制使得一些跨函式使用的資料,長度不固定的資料,生命週期不按呼叫走的資料就相當為難了。

於是我們需要堆記憶體,乙個更自由的記憶體結構,當然代價是維護成本高,效能也相對低下不少。

6樓:K.Scarlet

因為stack automata並不是Turing complete的。

假如你需要乙個Turing complete模型那麼你可以考慮把stack automata擴充套件為讀寫兩個stack。這樣其實就和用sbrk手動管理堆+stack segment沒什麼區別了。

另外其實擴充套件為兩個stack的pushdown automata同樣是Turing complete的,如果不需要額外的同步原語,那麼這個可能是答主所希望的解決方案。

7樓:lukeluke

我的天,這都不清楚,就敢來寫c艹?

彙編沒碰過吧?我猜是不屑於碰,老古董?

推測計算機原理不不了解,不提作業系統了。

看著是個小問題,但明眼人都能看出來同學你的基礎有多不牢!

8樓:babypapa

我對堆和棧的理解是,它們本身就是一種由作業系統所提供的記憶體池管理概念。

棧是一種快速的線性記憶體池,與執行緒繫結,無論申請和釋放記憶體,都只需要一條指令來完成,沒有任何代價。

堆,是與程序繫結的一種雜湊的記憶體池,向堆申請記憶體與釋放記憶體是有代價的,因為乙個作業系統下會有很多程序。

這兩種記憶體管理方式決定了使用場景:

棧通常用在短生命週期,用完立即歸還,不會跨執行緒的場景。

而堆,作用於乙個程序,可以被乙個程序中的所有執行緒訪問到,它甚至可以是跨程序的,在這裡申請與釋放記憶體是由使用者進行排程的。所以堆應該盡可能地使用在長生命週期的資料管理中,當然在很多短生命週期的場景中有時並不知道要多大的記憶體,也會使用堆,但乙個高效能是應該盡可能避免這種使用方式。

9樓:LoveCandy

這是計算機體系結構決定的問題,跟C++半毛錢關係都沒有。

別的先不說,沒有堆你告訴我怎麼實現變長陣列?難道根據應用場景決定編譯時用多大的buffer?不同場景編譯出不同版本的程式用?還是所有程式都用乙個超大buffer佔著茅坑不拉屎?

先把基礎打好,體系結構,彙編,作業系統基本概念先搞清楚再說。

話說得可能有點重,見諒。但道理確實是這個道理。

10樓:chenc

初學者其實不需要管什麼是堆什麼是棧。

我們把記憶體分為棧和堆是因為管理的方式不同。

棧一般只有乙個,編譯器用相對於棧頂的位置來簡單管理,不需要申請也不需要釋放,全自動的。開始的時候預留好棧區就可以用了。缺點就是因為棧頂的位置經常會變化,所以一般就只有引數、區域性變數這些在用棧,因為函式退出這些就不需要了,正好自動釋放。

除了棧剩下的都算是堆吧,需要自己申請,自己釋放,因為編譯器不知道你什麼時候要用,什麼時候不再使用。好處就是可以跨函式生命週期使用這些資料。

其實多數情況下,如果你使用的記憶體不算多,擔心申請釋放出問題的話,就用全域性或者靜態變數吧

11樓:張同

在任何時候(99.9%的情況下)優先使用heap。

除非:1。 棧的儲存空間是在編譯時確定的。如果儲存的資訊需要靜態變數之類的,就可以不使用棧。這些資訊是靜態的,所以一直存在。

2。 記憶體很富裕。棧的管理是有代價的,需要花費cpu時間。

如果記憶體很富裕,覺得不願意額外花時間管理記憶體,可以開乙個巨大的棧。比如chrome瀏覽器,每個網頁程序給個幾十M的儲存空間。

一句話,堆是時間換空間,棧是空間換時間。

另外:程式結束的時候棧和堆都會釋放的,函式結束的時候棧也會釋放。這個不是問題。

12樓:「已登出」

呼叫棧生命週期有限,在呼叫棧上分配的空間,生命週期不可能長於分配它的函式。

那麼就有這麼乙個問題,如果我需要分配空間,然後把這部分返回給呼叫者,在呼叫棧上分配的話,應該怎麼辦?

答案是,雖然實現方法可能各有差別,但總得來說,必須由呼叫者分配空間。

這就是乙個很神奇的操作了。因為呼叫者要麼必須預見到,要麼必須能夠從被呼叫者處獲得,這次呼叫所需要分配的全部的空間的大小。

這樣做一般來說會增加心智負擔,而且最壞的結果可能是,必須修改呼叫協定,並且付出額外的記憶體複製的代價。

所以在C,C++,以及絕大多數語言中,使用自由空間都比只用呼叫棧更好。

當然,只用棧是可能的,並且,所有使用自由空間能解決的問題,只用棧依然能解決……

但是除非特殊情況,你不會想嘗試後一種做法的。

13樓:

我感覺大家都是把堆疊的作用說出來,但是沒說到為什麼區分這兩者的目的。

總結:正常來說,棧可以都用堆來實現,沒問題。 也就是堆是通常理解的記憶體使用方法。

而用棧的目的:省記憶體、提高效率

比如乙個工程中 ,有很多子函式,裡面很多區域性變數,這些為什麼建議放到 stack裡面

因為編譯器會自動分析這些變數的使用時序,自動合併使用。 這個函式結束了,這塊記憶體就自動釋放了(沒有new delete過程,編譯器解析的時候自動實現分配的。執行時不占用處理週期,所以效率高)。

而換另一種方式

1 分配常駐記憶體(相當於每個前面加個static)這樣對記憶體消耗很大, 永遠不被釋放

2 用new delete 增加額外的開銷,效率會大大降低。

所以一般來說stack 的大小也要遠遠小於 heap

導致一種情況,如果使用的記憶體很大或者它的生命週期很長,盡量用heap. 不要為難編譯器。

由於stack 的生命週期是由編譯器優化的。 為了保證另外的程式能用到這部分記憶體的結果,有下面三種方式:

利用值傳遞、指標傳遞、引用傳遞的方式

2. 利用 static 限定的方式

3. 上面所說的用堆

為了效率盡量選擇1/2種,對C++來說當然是指標和引用效率更高, 但當使用記憶體比較大的時候、用heap(空間效率也是效率)

14樓:Xi Yang

有一天我不想自動釋放了,我需要乙個跨函式呼叫的區域,你怎麼辦?

並沒有遇到用堆比用棧更有優勢的情況

因為你根本沒寫過正經東西。

15樓:腹黑小太陽

1、棧夠用?棧是有大小限制的,你申請個1G記憶體嚐嚐stack overflow的滋味?

2、棧為什麼有大小限制?因為棧記憶體是程序開啟時就預先分配好的,給太多很浪費。

3、其它語言中自動管理的記憶體不是棧記憶體,而是加入了智慧型指標的堆記憶體。

4、為什麼要有棧記憶體,而不全部使用堆記憶體?因為要有一塊簡單的空間來存放函式呼叫的引數、返回值、上一級函式的現場。堆記憶體也是通過呼叫系統函式來實現分配的,都是堆記憶體就會陷入蛋生雞雞生蛋的悖論。

5、其它語言有沒有使用棧記憶體?有,不過被執行時裝起來了,你看不到。

6、所以為什麼系統層面不引入乙個全域性智慧型指標來管理堆記憶體?因為這不是系統應該插手的事情,智慧型指標用起來爽,出問題會很麻煩。

16樓:沙包妖夢

棧必須有,因為C++支援函式的呼叫和返回,那麼呼叫棧(不論叫什麼)都必須存在。

實際上(呼叫)是圖靈機的一部分。(其實不一定用棧實現,然而他們根本是等價的)

呼叫棧上面存放變數的功能可有可無,C++選擇實現這個功能,原因是用起來方便。

呼叫棧上的變數有2個特點:

進入函式時建立:不用自己new,保證建立成功,但不能有條件的建立

函式return時銷毀:除了複製乙個,沒有任何辦法留住他。

這意味著:

棧上的東西一般是函式內必須用到的,而不是某個if分支裡才用到。因為一旦呼叫就要初始化它。(當然,幾個int變數啥的都無所謂,主要是複雜型別)

特別適合那些只在這裡有用,離開就一點意義都沒有的變數,最著名的是迴圈變數i。

這些說的都是呼叫棧,「棧」本身可以在任何有關「上下文」的地方使用,絕不只是呼叫返回、放幾個變數那麼簡單。

堆是乙個抽象概念,只要有作業系統,就有記憶體管理,就有堆。

堆僅僅是一塊記憶體的抽象。執行在作業系統上的程式都是不能碰真正的記憶體的,只能碰堆裡的東西。

你可以粗略的認為:

堆全等於記憶體

(呼叫)棧是儲存在堆裡的乙個陣列

堆和樹有什麼區別?堆為什麼要叫堆,不叫樹呢?

Pober Wong 堆,是具有下列性質的完全二叉樹 每個節點的值都小於等於其左右孩子節點值是小根堆 大於等於則是大根堆 出自 資料結構 C 第二版 2011 6 第二版。出自大學課本,這應該算是比較權威的一種定義了。至於非要槓堆的概念的話,也沒有太大的意義。 尋回七里香 堆,也被稱為優先佇列,是按...

日語動詞中為什麼要分為 一類動詞 二類動詞 三類動詞 ,如何記憶和運用?

瓜瓜日語 為什麼要區分,主要是因為變形規則不一樣。我一般是從少到多記憶。首先明確所有的動詞都是以 段結尾。那麼最簡單的是三類,來 和以 結尾的動詞。其次是二類,我經常講乙個段子,你二不?當然不二。不日語就是 所以 以 段 和以 段 結尾的就是二類動詞。當然有些例外的需要特殊記憶。常見的有帰 切 散 ...

為什麼象棋圍棋等棋模擬賽要分為女子組和男子組呢?

Daniel 就說象棋,王天一特大和唐丹特大,王天一特大能在讓唐丹一先的情況下贏唐丹特大,下棋不僅僅是拼智商,還要拼體力,大腦的飛速運轉,要消耗大量的體力,我相信男女智商都是差不多的,但是體力差距還是挺大的 康老 大部分的圍棋比賽是不分性別的,如富士通 現已停辦 應氏盃 三星杯 等等圍棋世界大賽都是...