什麼是堆和棧,它們在哪兒?

聲明

正文

問題描述

編程語言書籍中常常解釋值類型被建立在棧上,引用類型被建立在堆上,可是並無本質上解釋這堆和棧是什麼。我僅有高級語言編程經驗,沒有看過對此更清晰的解釋。個人意思是我理解什麼是棧,可是它們究竟是什麼,在哪兒呢(站在實際的計算機物理內存的角度上看)?多線程

  1. 在一般狀況下由操做系統(OS)和語言的運行時(runtime)控制嗎?架構

  2. 它們的做用範圍是什麼?編程語言

  3. 它們的大小由什麼決定?函數

  4. 哪一個更快?oop

答案一

棧是爲執行線程留出的內存空間。當函數被調用的時候,棧頂爲局部變量和一些 bookkeeping 數據預留塊。當函數執行完畢,塊就沒有用了,可能在下次的函數調用的時候再被使用。棧一般用後進先出(LIFO)的方式預留空間;所以最近的保留塊 (reserved block)一般最早被釋放。這麼作可使跟蹤堆棧變的簡單;從棧中釋放塊(free block)只不過是指針的偏移而已。性能

堆(heap)是爲動態分配預留的內存空間。和棧不同,從堆上分配和從新分配塊沒有固定模式;你能夠在任什麼時候候分配和釋放它。這樣使得跟蹤哪部分堆已經被分配和被釋放變的異常複雜;有許多定製的堆分配策略用來爲不一樣的使用模式下調整堆的性能。學習

每個線程都有一個棧,可是每個應用程序一般都只有一個堆(儘管爲不一樣類型分配內存使用多個堆的狀況也是有的)。

直接回答你的問題: 1. 當線程建立的時候,操做系統(OS)爲每個系統級(system-level)的線程分配棧。一般狀況下,操做系統經過調用語言的運行時 (runtime)去爲應用程序分配堆。 2. 棧附屬於線程,所以當線程結束時棧被回收。堆一般經過運行時在應用程序啓動時被分配,當應用程序(進程)退出時被回收。 3. 當線程被建立的時候,設置棧的大小。在應用程序啓動的時候,設置堆的大小,可是能夠在須要的時候擴展(分配器向操做系統申請更多的內存)。 4. 棧比堆要快,由於它存取模式使它能夠輕鬆的分配和從新分配內存(指針/整型只是進行簡單的遞增或者遞減運算),然而堆在分配和釋放的時候有更多的複雜的 bookkeeping 參與。另外,在棧上的每一個字節頻繁的被複用也就意味着它可能映射處處理器緩存中,因此很快(譯者注:局部性原理)。

答案二

Stack:

  1. 和堆同樣存儲在計算機 RAM 中。

  2. 在棧上建立變量的時候會擴展,而且會自動回收。

  3. 相比堆而言在棧上分配要快的多。

  4. 用數據結構中的棧實現。

  5. 存儲局部數據,返回地址,用作參數傳遞。

  6. 當用棧過多時可致使棧溢出(無窮次(大量的)的遞歸調用,或者大量的內存分配)。

  7. 在棧上的數據能夠直接訪問(不是非要使用指針訪問)。

  8. 若是你在編譯以前精確的知道你須要分配數據的大小而且不是太大的時候,可使用棧。

  9. 當你程序啓動時決定棧的容量上限。

Heap:

  1. 和棧同樣存儲在計算機RAM。

  2. 在堆上的變量必需要手動釋放,不存在做用域的問題。數據可用 delete, delete[] 或者 free 來釋放。

  3. 相比在棧上分配內存要慢。

  4. 經過程序按需分配。

  5. 大量的分配和釋放可形成內存碎片。

  6. 在 C++ 中,在堆上建立數的據使用指針訪問,用 new 或者 malloc 分配內存。

  7. 若是申請的緩衝區過大的話,可能申請失敗。

  8. 在運行期間你不知道會須要多大的數據或者你須要分配大量的內存的時候,建議你使用堆。

  9. 可能形成內存泄露。

舉例:

int foo()
{
    char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
    bool b = true; // Allocated on the stack.
    if(b)
    {
        //Create 500 bytes on the stack
        char buffer[500];

        //Create 500 bytes on the heap
        pBuffer = new char[500];

    }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

答案三

堆和棧是兩種內存分配的兩個統稱。可能有不少種不一樣的實現方式,可是實現要符合幾個基本的概念:

1.對棧而言,棧中的新加數據項放在其餘數據的頂部,移除時你也只能移除最頂部的數據(不能越位獲取)。

khqDF

2.對堆而言,數據項位置沒有固定的順序。你能夠以任何順序插入和刪除,由於他們沒有「頂部」數據這一律念。

E5QTV

上面上個圖片很好的描述了堆和棧分配內存的方式。

在一般狀況下由操做系統(OS)和語言的運行時(runtime)控制嗎?

如前所述,堆和棧是一個統稱,能夠有不少的實現方式。計算機程序一般有一個棧叫作調用棧,用來存儲當前函數調用相關的信息(好比:主調函數的地址,局部變量),由於函數調用以後須要返回給主調函數。棧經過擴展和收縮來承載信息。實際上,程序不是由運行時來控制的,它由編程語言、操做系統甚至是系統架構來決定。

堆是在任何內存中動態和隨機分配的(內存的)統稱;也就是無序的。內存一般由操做系統分配,經過應用程序調用 API 接口去實現分配。在管理動態分配內存上會有一些額外的開銷,不過這由操做系統來處理。

它們的做用範圍是什麼?

調用棧是一個低層次的概念,就程序而言,它和「做用範圍」沒什麼關係。若是你反彙編一些代碼,你就會看到指針引用堆棧部分。就高級語言而言,語言有它本身的範圍規則。一旦函數返回,函數中的局部變量會直接直接釋放。你的編程語言就是依據這個工做的。

在堆中,也很難去定義。做用範圍是由操做系統限定的,可是你的編程語言可能增長它本身的一些規則,去限定堆在應用程序中的範圍。體系架構和操做系統是使用虛擬地址的,而後由處理器翻譯到實際的物理地址中,還有頁面錯誤等等。它們記錄那個頁面屬於那個應用程序。不過你不用關心這些,由於你僅僅在你的編程語言中分配和釋放內存,和一些錯誤檢查(出現分配失敗和釋放失敗的緣由)。

它們的大小由什麼決定?

依舊,依賴於語言,編譯器,操做系統和架構。棧一般提早分配好了,由於棧必須是連續的內存塊。語言的編譯器或者操做系統決定它的大小。不要在棧上存儲大塊數據,這樣能夠保證有足夠的空間不會溢出,除非出現了無限遞歸的狀況(額,棧溢出了)或者其它不常見了編程決議。

堆是任何能夠動態分配的內存的統稱。這要看你怎麼看待它了,它的大小是變更的。在現代處理器中和操做系統的工做方式是高度抽象的,所以你在正常狀況下不須要擔憂它實際的大小,除非你必需要使用你尚未分配的內存或者已經釋放了的內存。

哪一個更快一些?

棧更快由於全部的空閒內存都是連續的,所以不須要對空閒內存塊經過列表來維護。只是一個簡單的指向當前棧頂的指針。編譯器一般用一個專門的、快速的寄存器來實現。更重要的一點事是,隨後的棧上操做一般集中在一個內存塊的附近,這樣的話有利於處理器的高速訪問(譯者注:局部性原理)。

答案四

你問題的答案是依賴於實現的,根據不一樣的編譯器和處理器架構而不一樣。下面簡單的解釋一下:

  1. 棧和堆都是用來從底層操做系統中獲取內存的。

  2. 在多線程環境下每個線程均可以有他本身徹底的獨立的棧,可是他們共享堆。並行存取被堆控制而不是棧。

堆:

  1. 堆包含一個鏈表來維護已用和空閒的內存塊。在堆上新分配(用 new 或者 malloc)內存是從空閒的內存塊中找到一些知足要求的合適塊。這個操做會更新堆中的塊鏈表。這些元信息也存儲在堆上,常常在每一個塊的頭部一個很小區域。

  2. 堆的增長新快一般從低地址向高地址擴展。所以你能夠認爲堆隨着內存分配而不斷的增長大小。若是申請的內存大小很小的話,一般從底層操做系統中獲得比申請大小要多的內存。

  3. 申請和釋放許多小的塊可能會產生以下狀態:在已用塊之間存在不少小的空閒塊。進而申請大塊內存失敗,雖然空閒塊的總和足夠,可是空閒的小塊是零散的,不能知足申請的大小,。這叫作「堆碎片」。

  4. 當旁邊有空閒塊的已用塊被釋放時,新的空閒塊可能會與相鄰的空閒塊合併爲一個大的空閒塊,這樣能夠有效的減小「堆碎片」的產生。

0Obi0

棧:

  1. 棧常常與 sp 寄存器(譯者注:"stack pointer",瞭解彙編的朋友應該都知道)一塊兒工做,最初 sp 指向棧頂(棧的高地址)。

  2. CPU 用 push 指令來將數據壓棧,用 pop 指令來彈棧。當用 push 壓棧時,sp 值減小(向低地址擴展)。當用 pop 彈棧時,sp 值增大。存儲和獲取數據都是 CPU 寄存器的值。

  3. 當函數被調用時,CPU使用特定的指令把當前的 IP (譯者注:「instruction pointer」,是一個寄存器,用來記錄 CPU 指令的位置)壓棧。即執行代碼的地址。CPU 接下來將調用函數地址賦給 IP ,進行調用。當函數返回時,舊的 IP 被彈棧,CPU 繼續去函數調用以前的代碼。

  4. 當進入函數時,sp 向下擴展,擴展到確保爲函數的局部變量留足夠大小的空間。若是函數中有一個 32-bit 的局部變量會在棧中留夠四字節的空間。當函數返回時,sp 經過返回原來的位置來釋放空間。

  5. 若是函數有參數的話,在函數調用以前,會將參數壓棧。函數中的代碼經過 sp 的當前位置來定位參數並訪問它們。

  6. 函數嵌套調用和使用魔法同樣,每一次新調用的函數都會分配函數參數,返回值地址、局部變量空間、嵌套調用的活動記錄都要被壓入棧中。函數返回時,按照正確方式的撤銷。

  7. 棧要受到內存塊的限制,不斷的函數嵌套/爲局部變量分配太多的空間,可能會致使棧溢出。當棧中的內存區域都已經被使用完以後繼續向下寫(低地址),會觸發一個 CPU 異常。這個異常接下會經過語言的運行時轉成各類類型的棧溢出異常。(譯者注:「不一樣語言的異常提示不一樣,所以經過語言運行時來轉換」我想他表達的是這個含義)

9UshP

*函數的分配能夠用堆來代替棧嗎?

不能夠的,函數的活動記錄(即局部或者自動變量)被分配在棧上,這樣作不但存儲了這些變量,並且能夠用來嵌套函數的追蹤。

堆的管理依賴於運行時環境,C 使用 malloc ,C++ 使用 new,可是不少語言有垃圾回收機制。

棧是更低層次的特性與處理器架構緊密的結合到一塊兒。當堆不夠時能夠擴展空間,這不難作到,由於能夠有庫函數能夠調用。可是,擴展棧一般來講是不可能的,由於在棧溢出的時候,執行線程就被操做系統關閉了,這已經太晚了。

譯者注

關於堆棧的這個帖子,對我來講,收穫很是多。我以前看過一些資料,本身寫代碼的時候也經常思考。就這方面,也和祥子(個人大學舍友,如今北京郵電讀研,技術牛人)探討過屢次了。可是終究是一個一個的知識點,這個帖子看完以後,豁然開朗,把知識點終於鏈接成了一個網。這種感受,經歷過的必定懂得,期間的興奮不言而喻。

這個帖子跟帖者很多,我選了評分最高的四個。這四個之間也有一些是重複的觀點。我的鐘愛第四個回答者,我看的時候,瞬間高潮了,有木有?不過須要一些彙編語言、操做系統、計算機組成原理的的基礎,知道那幾個寄存器是幹什麼的,要知道計算機的流水線指令工做機制,保護/恢復現場等概念。三個回覆者都涉及到了操做系統中虛擬內存;在比較速度的時候,你們必定要在腦中對「局部性原理」和計算機高速緩存有一個概念。

若是你把這篇文章看懂了,我相信你收穫的不僅是堆和棧,你會理解的更多!

興奮之餘,有幾點仍是要強調的,翻譯沒有逐字逐詞翻譯,大部分是經過我我的的知識積累和對回帖者的意圖揣測而來的。請你們不要咬文嚼字,逐個推敲,咱們的目的在於技術交流,不是麼?達到這一目的就夠了。

下面是一些不肯定點:

  1. 我沒有聽過 bookkeeping data 這種說法,故沒有翻譯。從上下文理解來看,能夠想成是用來寄存器值?函數參數?返回地址?若是有了解具體含義的朋友,煩請告知。

  2. 棧和堆棧是一回事,英文表達是 stack,堆是 heap。

  3. 調用棧的概念,我是第一次據說,不太熟悉。你們能夠去查查資料研究一下。

以上,送給你們,本文結束。

相關文章
相關標籤/搜索