動態記憶體管理

為什麼需要堆積(生命期不綁定範圍)

把一個函式想成一張有桌面的工作檯。函式被呼叫時,出現一張乾淨的桌子;你把區域變數攤在上面。函式返回時,整張桌子瞬間被收走——桌上的一切都消失。那張桌子就是堆疊(stack),它極為方便:你永遠不必善後。但它有一條硬性規則:桌上的任何東西在函式結束的那一刻就沒了。那麼,要做出一個必須比建立它的函式活得更久的東西該怎麼辦?這正是堆積(heap)所回答的問題。

堆積是程式記憶體中一塊獨立的區域,其中物件的生命期不綁定任何範圍(scope)。當你在堆積上配置時,這塊記憶體會跨越函式返回、跨越迴圈,想活多久就活多久——直到你這位程式設計師明確釋放它為止。具體地說:若一個函式建立了一個串列並想把它交還給呼叫者,它不能用區域堆疊變數來建,因為那些在返回時就死了。它應改用 malloc() 在堆積上配置該串列,然後回傳指標。呼叫者收到一個指向仍然存活之記憶體的有效指標。初學者的經典錯誤正好相反:回傳區域變數的位址——一個指向已被收走之桌子的指標——這是懸置指標(dangling pointer)與未定義行為。

於是堆積與堆疊分工。生命剛好裝進一次函式呼叫內的東西就用堆疊:它快又自動。當物件的生命期必須由你決定、而非由呼叫結構決定時——當它必須持續存在、成長、或在程式中不同時間來去的部分之間共享時——就用堆積。這份自由的代價是:堆積不是自動的,每一筆配置都歸你所有,你必須一一歸還。

/* 錯誤:回傳懸置指標 */ int *bad(void) { int x = 42; return &x; } /* x 在返回時就死了 */ /* 正確:堆積物件比函式活得久 */ int *good(void) { int *p = malloc(sizeof(int)); *p = 42; return p; /* 呼叫者現在擁有 p,之後負責釋放 */ }

堆疊版本回傳指向已不存在記憶體的指標;堆積版本回傳能存活下來的記憶體。

這裡的堆積是行程記憶體中供動態配置用的區域——不是堆積資料結構(優先佇列那種樹)。兩者同名但毫無關係。

又稱
heap region堆積區free store