https://blog.gtwang.org/programming/memory-layout-of-c-program/
在使用 C 語言開發比較低階的程式之前,多少都要了解一下程式實際在執行時的記憶體配置情況,下圖為典型的 C 語言程式在執行時的記憶體配置圖,記憶體的使用主要可分為 text、data、bss、stack、heap 與 system 這幾個部分。
text:程式碼
文字區段(text segment)也稱為程式碼區段(code segment),這裡存放的是可執行的 CPU 指令(instructions)。
這個區段通常位於 heap 或 stack 之後,避免因 heap 或 stack 溢位而覆寫 CPU 指令。
通常文字區段的資料是可以共用的,當多個同樣的程式在執行時,在記憶體中只需要存有一份就夠了,而這個文字區段通常都是唯讀的,避免程式本身誤改了自己的 CPU 指令。
data:初始化靜態變數
初始化資料區段(initialized data segment)儲存的是一些已經初始化的靜態變數,例如有經過初始化的 C 語言的全域變數(global variables)以及靜態變數(static variables)都是儲存於此處。
這個區段的變數又可分為唯讀區域(read-only area)以及可讀寫區域(read-write area),可讀寫區域用於存放一般變數,其資料會隨著程式的執行而改變,而唯讀區域則是存放固定的常數。
bss:未初始化靜態變數
未初始化資料區段(uninitialized data segment)又稱為 bss 區段(這個名稱的起源來自於古老的組譯器,代表 block started by symbol)是儲存尚未被初始化的靜態變數,而這些變數在程式執行之前會被系統初始化為 0 或是 null。
stack:區域變數
堆疊區段(stack segment)用於儲存函數的區域變數,以及各種函數呼叫時需要儲存的資訊(例如函數返回的記憶體位址還有呼叫者函數的狀態等),每一次的函數呼叫就會在堆疊區段建立一個 stack frame,儲存該次呼叫的所有變數與狀態,這樣一來同一個函數重複被呼叫時就會有不同的 stack frame,不會互相干擾,遞迴函數就是透過這樣的機制來執行的。
heap:動態配置變數
heap 區段的記憶體空間用於儲存動態配置的變數,例如 C 語言的 malloc 以及 C++ 的 new 所建立的變數都是儲存於此。
堆疊區段一般的狀況會從高記憶體位址往低記憶體位址成長,而 heap 剛好從對面以相反的方向成長。
system:命令列參數與環境變數
system 區段用於儲存一些命令列參數與環境變數,這部分會跟系統有關。
實際範例
這是一個最簡單的 C 語言程式:
#include <stdio.h> int main() { return 0; }
編譯之後,可使用
size 查看它的內部記憶體配置:gcc source.c size a.out
text data bss dec hex filename 1099 544 8 1651 673 a.out
新增一個未初始化的全域靜態變數:
#include <stdio.h> double global[30]; // 儲存於 bss 的未初始化靜態變數 int main() { return 0; }
查看內部記憶體配置:
gcc source.c size a.out
text data bss dec hex filename 1099 544 272 1915 77b a.out
這一個未被初始化的陣列被放在 bss 區段。
接著增加一個已初始化的靜態變數:
#include <stdio.h> int main() { // 儲存於 data 的已初始化靜態變數 static int x[5] = {1, 2, 3, 4, 5}; return 0; }
查看內部記憶體配置:
gcc source.c size a.out
text data bss dec hex filename 1099 564 4 1667 683 a.out
有初始化的靜態變數或全域變數都會被放進 data 區段中。
儲存於 stack 與 heap 的變數在這裡看不到,以下是儲存於各種區段的變數:
#include <stdio.h> const int global_x = 1; // 儲存於 data 區段(唯讀區域) int global_y = 1; // 儲存於 data 區段(可讀寫區域) int global_z; // 儲存於 bss 區段 int main() { const static int x = 1; // 儲存於 data 區段(唯讀區域) static int y = 1; // 儲存於 data 區段(可讀寫區域) static int z; // 儲存於 bss 區段 int w = 1; // 儲存於 stack 區段 // 儲存於 heap 區段 char *buf = (char*) malloc(sizeof(char) * 100); // ... free(buf); return 0; }
參考書籍:
- Randal E. Bryant and David R. O’Hallaron. 2010. Computer Systems: A Programmer’s Perspective (2nd ed.). Addison-Wesley Publishing Company, USA.
- W. Richard Stevens and Stephen A. Rago. 2013. Advanced Programming in the UNIX Environment (3rd ed.). Addison-Wesley Professional
Stack 與 Heap 有何差別
https://antrash.pixnet.net/blog/post/70456505
三分天下。程式執行過程中其實主要分成三大區塊:global、stack、heap三塊。其中global區塊最最易理解,主要存放全域變數或宣告為static的靜態變數在此就不多做贅述;另外兩個區塊分別為stack跟heap這兩者往往混淆不清,尤其在java中有時候會出現stack overflow或heap overflow到底兩者差異在哪,若工程師連這都不清楚那以後怎麼去調整JVM中的heap memory space跟stack memory space的大小呢?
貼心的系統全自動化管理區塊:Stack Memory Space!在記憶體中不外乎就是要存放變數、函式相關資訊等資料,使運作過程可以順利取得所需的變數或函式所在地。要讓系統可以全自動化管理,代表需可被預期此變數或函數資訊的生命週期,一旦完全可預測代表可以安心的交由系統管理,這些資訊也將在執行過程中被存放在stack空間。Stack中常見的存放資訊如下:區域變數(local variable)、函式參數(function/method parameter)、函數的返回位址(function/method return address)等資訊。為何上述資訊會放於stack之中,簡單來說:
void method1() {
int x = 100;
}
上述的int x = 100,系統會在stack中找一個區塊給x,另外裡面的內容為100。然而,x會被放入stack主要是因為在編譯時期系統已經可以預知x從何時開始配置跟何時結束回收(當然就是看所屬block結束就跟著回收),由於配置跟回收的規則明確,當然就往stack擺囉。
在舉一例子:
void method2() {
method1();
}
上述當呼叫method1()時,系統會先把method2的返回位址存到stack當中,為何是存放在stack呢,因為函式的呼叫有後進先出的概念,當method1()被呼叫而開始執行,待結束時必定會查找該返回何處,故最後一定會讀取函式的返回位址,既然如此明確而有條理,當然也是往stack放!
可預測性外加後進先出的生存模式,令stack無疑是最佳的存放策略。由於程式語言中變數跟函式的生命週期皆為後進先出的概念,也就是越晚產生的會越先被回收或銷毀。正因如此只要是可預測性的相關資訊都是往stack存放。此外,由於stack中的資料之存活週期規律故由系統自行產生與回收其空間即可,就不勞工程師們費心啦!
天啊!程式中竟然有不可預測其存活時間的資料存在。在程式中,有部分的需求總是在執行中依據實際情況才會動態增減,這些資訊是難以被預測哪時候開始有?量有多少?何時該回收?…這些不可預測的因素造成上述的stack區塊不適合運用於此。當資訊為動態配置產生,系統會存放在另外一塊空間,稱之為『Heap』(注意這裡的Heap跟資料結構中的Heap不相關,可別會錯意!)。Heap的區塊專收執行期間動態產生的資料,由於為動態產生故結束點無法由系統來掌握,故需使用者自行回收空間。在C++或Java中利用new語法產生的就是動態配置的物件,需存放於heap中。
奇怪跑越久記憶體用越多的怪現象。許多時候執行的程式都沒有改變,但卻常出現隨時間執行越久程式所耗用的空間將越多,最後造成out of memory。工程師也不知為何如此,就是定期在out of memory之前restart程式即可。這中現象層出不窮,一般大多是因為工程師沒有正確將記憶體回收所導致。Heap中的資料如果沒有正常的回收,將會逐步成長到將記憶體消耗殆盡,下次發生上述問題的實後,切記自己檢查一下heap空間的資料有無正常回收。論述到此有些讀者可能會覺得納悶:為何在寫Java都不需要注意回收空間的問題?~答案是因為Java中會採用Garbage Collection(垃圾回收)的機制自動檢查Heap中哪些資料已經沒有被使用,當確認資料已經沒有使用會自動將空間回收,如此工程師就專注撰寫程式即可,不用擔心記憶體回收不當等問題。
The conclusion is…。當產生stack overflow一般是因為過多的函式呼叫(例如:遞迴太深)、或區域變數使用太多,此時請試著將stack size調大一點,另外檢查看看函式的呼叫跟變數的使用量。反之,當發生heap overflow請檢查是否都有正確將heap space的資料回收,另外採行的動態配置是否合理,不要過渡濫用而new出無謂的空間,若真的是程式過於複雜造成,請將heap size調大一些。
https://nwpie.blogspot.com/2017/05/5-stack-heap.html
簡單來說, 從記憶體配置的角度, 用一個二分法
stack 用於靜態記憶體配置, 大陸翻譯為棧, 棧, 棧 (why ?)
heap 用於動態記憶體配置, 大陸翻譯為堆
引用
https://www.codeproject.com/Articles/76153/Six-important-NET-concepts-Stack-heap-value-types#Stack%20and%20Heap
public void Method1()
{
// Line 1
int i=4;
// Line 2
int y=2;
//Line 3
class1 cls1 = new class1();
}
總結是 :
- value type 的變數, 包括指標變數會放在 stack
- reference type 的變數 (如 string, object) 本身也會放 stack, 然而他的值 (value) 則是放 heap
- box 就是 value types to reference types 的過程, 所以 value 會被放到 heap 中, 而產生一個 object 變數來指向這個 value, 變數指標則是在 stack
- unbox 是 reference types to value types 的過程, 所以原本 object 所指向的值 (heap 中) 會被複製到 stack 中並賦予明確 value type 型別


