# 記憶體分配:stack與heap
記憶體的功用,是提供處理器存取資料,在需要時再進行存取。因此,記憶體對於每個程式而言,都是珍貴的資源。而負責分配記憶體的工作,是由作業系統負責。本文將介紹有關記憶體在一個程式中的分配。
## 程式的記憶體分配

> 記憶體分配的示意圖。[圖源](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
<!-- 你好像不小心開到編輯權限了 -->