【C++ 筆記】動態記憶體(new / delete) - part 29
【C++ 筆記】動態記憶體(new / delete) - part 29
很感謝你點進來這篇文章。
你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!
動態記憶體(Dynamic Memory)
什麼是動態記憶體?
當我們宣告一個變數的時候,編譯器會依據這個變數所屬的資料型態,自動配置其記憶體空間。這些資源都是配置於記憶體的堆疊區(stack),生命週期僅止於函數執行期間,當函數執行完成後就會自動清除。
另外,一旦配置後,就不能被刪除或更改他的大小。所以這時候動態記憶體就出現了。
在 C++ 中,記憶體分為兩部分(from 菜鳥教程):
- 堆疊區(stack):在函數內部宣告的所有變數都將佔用堆積記憶體。
- 堆積區(heap):程式中未使用的記憶體,在程式執行時可用於動態配置記憶體。
動態記憶體配置是在程式執行時配置記憶體的過程,這可以讓開發者在程式執行期間預留一些記憶體,依據開發者的需求去用它,然後再把記憶體給釋放以用於其他目的。
而上述所預留的「記憶體」就是所謂的「堆積區」記憶體。
動態記憶體的用處
- 當你不確定一個陣列的大小時。
- 可用來實作如 linked-list, trees 等這些資料結構。
- 於需要高效率的記憶體管理的複雜程式當中。
動態記憶體的實作方式
C++ 提供兩種運算子,用於動態記憶體的配置與釋放:
- 配置:
new。 - 釋放:
delete。
new / delete 運算子
以下是 new 運算子的通用語法:
1 | new data-type |
data-type 可為任意內建資料型態,class、struct 這兩個自訂資料型態也可以。
先來看個簡單的小範例:
1 |
|
Output:
1 | 數值為: 10 |
以上用 new 運算子配置一個內建的資料型態 int,值為 10,給指標 p。
為什麼 new 所配置的記憶體通常要用指標接收呢?如果不用 new,而是直接宣告變數並賦值,如 int x = 10;,這樣變數會配置在堆疊區(stack),而不是堆積區(heap),就不是動態記憶體配置了,自然失去使用 new 的意義。
另外 new int(10) 會回傳 int* 型態,你不用指標也不行。
最後要有個好習慣,就是寫 delete 手動釋放記憶體,避免記憶體洩漏。
陣列的動態記憶體配置
先來看範例:
1 |
|
Output:
1 | 0 2 4 6 8 |
要為陣列做動態記憶體配置,需要將 new int(5) 寫成 new int[5],表示要對陣列做動態記憶體配置。
因此在釋放記憶體的時候,也要寫成 delete[],避免未定義行為。
二維陣列的動態記憶體配置
二維陣列的動態記憶體配置就複雜了一點,int** arr = new int*[rows]; 就用到了雙重指標(指向指標的指標:int**),讓每一列(arr[i][j] 的 [i])都是 int* 型態。
之後還要再配置一次,就是下面的 for loop,讓每一行(arr[i][j] 的 [j])都配置到。
1 |
|
Output:
1 | 陣列內容 : |
那…三維陣列呢?
就是三重指標(int***),然後再跑雙層迴圈配置動態記憶體,如下所示:
1 | int m = 5; |
物件的動態記憶體配置
基本上跟簡單的內建資料型態沒啥差別。
1 |
|
Output:
1 | 姓名 : LukeTseng, 年齡 : 18 |
執行時記憶體不夠了怎麼辦?
若堆積區中沒有足夠的記憶體可以去配置,還繼續用 new 去配置的話,就會拋出例外 std::bad_alloc,除非將 nothrow 與 new 運算子一起使用,會回傳 nullptr。
那在使用程式前,nothrow 跟 new 一起使用可以用來做個檢查,如:
1 | int *p = new (nothrow) int; |
From GeeksForGeeks。
跟動態記憶體有關的一些錯誤
記憶體洩漏(Memory Leaks)
這其實就是最後沒把記憶體釋放的結果,所以要養成好習慣,在程式結束前用 delete 釋放掉記憶體。
另外如果記憶體位址遺失,記憶體會一直保持配置狀態(與上述狀態相同)直到程式執行。
那有哪些狀況是記憶體位址遺失呢?
- 指標被覆蓋或重新指定
1 | int* p = new int(10); |
- 指標變數離開作用域(Scope)
1 | void foo() { |
- 動態陣列的部分元素位址遺失
1 | int* arr = new int[5]; |
- 指標遺失於容器中或函式回傳錯誤方式
1 | int* create() { |
C++ 11 有 std::unique_ptr 、 std::shared_ptr 等類別,稱為 smart pointer,可更好的協助動態記憶體配置,礙於篇幅,本篇暫不談。
迷途指標(Dangling Pointers)
在 C++ 中,迷途指標(Dangling Pointer)是指「指向無效記憶體位址的指標」。這種情況通常發生在指標曾指向一個合法的記憶體位址,但那塊記憶體已經被釋放或超出作用範圍,而指標本身還存在,造成錯誤的存取行為。
哪些是迷途指標的成因呢?
- 指標指向已被 delete 的記憶體
1 | int* p = new int(10); |
- 指標指向作用域外的區域變數
1 | int* getPointer() { |
- 多個指標指向同一記憶體,卻重複釋放
1 | int* p1 = new int(30); |
用個白話的例子來說明迷途指標:假設指標是一把鑰匙,記憶體是你的房子,然後有一天惠惠發神經用爆裂魔法把你家炸了(記憶體釋放),此時的你如同迷途的羔羊,站在你家門前,喔不,你已經沒門了XD,然後你手舉著鑰匙還想要開門,這就是迷途指標。
解決方式有兩種:
- 用 nullptr 初始化指標,釋放記憶體後再次指定為 nullptr。
- 用 smart pointer。(
std::unique_ptr、std::shared_ptr)
雙重釋放(Double Deletion)
顧名思義就是對同一塊動態配置的記憶體執行兩次 delete 或 delete[]。
解決方式與迷途指標相同。
new / delete與 malloc() / free() 混用
malloc()、free() 是 C-style 的動態記憶體配置與釋放,只能選擇 C++ style 或 C-style 一個使用,因為這兩個都不相容。
另外 C++ 也有支援上述兩個函數,但用 new 跟 delete 會比那兩個函數好、又安全。
總結
C++ 記憶體分成兩大塊區域:
| 區域 | 說明 |
|---|---|
| 堆疊區 stack | 函式內部變數,生命週期短,編譯器自動配置與釋放。 |
| 堆積區 heap | 執行期間可手動配置與釋放的記憶體空間,用於動態記憶體。 |
new / delete:
| 操作 | 功能 |
|---|---|
new | 在堆積區配置記憶體,回傳指標。 |
delete | 釋放 new 配置的記憶體,避免記憶體洩漏(所以記得每次程式結束前要釋放記憶體)。 |
:::info
為什麼 new 要搭配指標使用?
因為 new 回傳的是指向堆積區的記憶體位址,必須用指標來接收,否則會失去動態記憶體配置的意義。
:::
單一變數的配置:
1 | int* p = new int(10); |
陣列配置:
1 | int* arr = new int[5]; |
二維陣列:
- 配置
1 | int** arr = new int*[rows]; |
- 釋放
1 | for (int i = 0; i < rows; ++i) |
三維陣列:
- 配置
1 | int*** arr = new int**[m]; |
- 釋放
1 | for (int i = 0; i < m; ++i) { |
物件的配置:
1 | Student* s = new Student("LukeTseng", 18); |
例外處理(記憶體不足):
1 | int* p = new (nothrow) int; |
常見錯誤
| 問題類型 | 說例 |
|---|---|
| 記憶體洩漏 | 忘記釋放、或位址遺失:p = new int(20); // 沒釋放舊的。 |
| 迷途指標 | 使用已釋放或作用域外的指標:int* p = new int; delete p; *p = 5;。 |
| 雙重釋放 | 對同一記憶體重複 delete:delete p1; delete p2;(若 p1 == p2)。 |
| 混用 new/free | new 必須配 delete,malloc() 必須配 free(),不可交叉使用。 |
解決方案
| 做法 | 說明 |
|---|---|
指標初始化為 nullptr | 可避免未定義行為與迷途指標。 |
| 使用 smart pointer | std::unique_ptr、shared_ptr 等更安全的管理方式。 |
參考資料
[Day 04] 用C++ 設計程式中的系統櫃:動態配置記憶體 | iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
new and delete Operators in C++ For Dynamic Memory - GeeksforGeeks
bad_alloc in C++ - GeeksforGeeks
Memory leak in C++ - GeeksforGeeks


