# 記憶體分配:stack與heap 記憶體的功用,是提供處理器存取資料,在需要時再進行存取。因此,記憶體對於每個程式而言,都是珍貴的資源。而負責分配記憶體的工作,是由作業系統負責。本文將介紹有關記憶體在一個程式中的分配。 ## 程式的記憶體分配 ![](https://i.imgur.com/OF4kiXQ.png) > 記憶體分配的示意圖。[圖源](https://visualgdb.com/documentation/embedded/stackheap/) 一個程式在分配到記憶體後,會被分配為四個部分: Code, Static/Global, Stack和Heap。 * Code的部分會儲存程式所需要的指令。 * Static/Global的部分儲存全域變數,用於程式中跨越多個函數皆需要的變數。 * Stack的部分用來儲存函式的呼叫和區域變數的資訊,在函式結束會被釋放。 * Heap是可以用來動態分配的記憶體。 ## Stack 介紹 我們以以下程式為例: ```c= #include<stdio.h> using namespace std; int total = 0; int bar(int z){ total++; return z*z; } int foo(int x,int y){ total++; int k = bar(x+y); return k; } int main(){ total++; int a = 1; int b = 2; int c = foo(a,b); printf("%d \n",c); } ``` 當電腦呼叫main()後,main()呼叫了foo(),foo()又呼叫了bar()。如果以記憶體的觀點,實際做到的事情如下: 1. total變數放入global。 2. main()被呼叫,將main()相關的資訊推入Stack。 :::spoiler 有關推入stack的資訊 >在一個函式進入stack時,會推入函式的回傳地址、區域變數的資訊和函式的參數。這一系列在stack中的資訊,稱為stack-frame ::: 3. main()呼叫foo(a,b),將foo()的stack-frame推入stack,將程式控制權暫時移轉給foo()。 4. foo()呼叫bar(x+y),同樣將bar()的stack-frame推入stack,移轉控制權。 5. bar()結束,回傳資訊到回傳地址,並將bar()的stack-frame pop掉,控制權回到foo()。 6. foo()結束,回傳資料到回傳地址,並將foo()的stack-frame pop掉,控制權回到main()。 7. main()結束,將main()的stack-frame pop掉,控制權移轉回作業系統。 8. 釋放global的資訊 由於stack不會隨著程式的增長而增加,所以如果函式呼叫的深度過大,stack的容量會不夠,此時會導致程式崩潰,此現象稱為stack-overflow,通常在不小心出現無限遞迴時發生。 因為stack的固定大小,如果我們要宣告一個大陣列,宣告在區域變數會有些問題,其中一個解法是將陣列宣告在global。然而,由於global的資料在程式一開始就佔據,我們無法隨著程式的進行而動態分配記憶體大小,讓空間被浪費。因此,heap就是被用來處理分配動態記憶體的問題。 ## heap介紹 heap又被稱為動態記憶體(dynamic memory)。有別於stack,heap的大小在程式的運行中不固定,因此我們可以自由地請求或釋放heap的記憶體。(當然,這同時也是件危險的事,畢竟你要注意記憶體是否被用完) 在C中使用heap,會需要以下函式: * malloc() * calloc() * realloc() * free() 以下面的程式碼為例: ```c= #include<stdio.h> #include<stdlib.h> using namespace std; int main(){ int a = 1; int* p; p = (int*) malloc(sizeof(int)); *p = 10; //free(p); p = (int*) malloc(sizeof(int)); *p = 20; } ``` 在main()的stack-frame中,會包含區域變數a和指標p。在第一次的malloc被呼叫後,在heap中會被請求4bytes(int的大小)的空間(假設開頭的位置在0x0400),並且p會指向0x0400。在第八行即是設定該位置的值。 在第二次呼叫malloc()時,又會再次請求4bytes的記憶體(假設位置在0x0100),此時p會改為指向0x0100的位置。 然而,即便原本0x0001的位置沒有指標指向它,他仍會佔據heap的空間,因此,要有效地管理記憶體,必須執行free()來釋放無用的記憶體。 ## 讓我們塞爆heap吧! 在稍早的示意圖中,可以看到stack和heap是相連的,只是一個向下生長,一個向上生長。如果我們能利用一些黑魔法讓資料超出heap,似乎有可能可以改寫到stack的內容,而這種攻擊方式稱為buffer-overflow。 ## 參考資料 https://www.youtube.com/watch?v=_8-ht2AKyH4&ab_channel=freeCodeCamp.org https://www.youtube.com/watch?v=Q2sFmqvpBe0&ab_channel=ComputerScience https://zh.wikipedia.org/wiki/%E5%91%BC%E5%8F%AB%E5%A0%86%E7%96%8A https://www.laioffer.com/zh/news/2017-06-22-what-is-call-stack/ https://en.wikipedia.org/wiki/Call_stack