C++ new和delete(C++動態分配和釋放記憶體)

2020-07-16 10:04:23
陣列的長度是預先定義好的,在整個程式中固定不變。C++ 不允許定義元素個數不確定的陣列。例如:
int n;
int a[n];  //這種定義是不允許的
但是在實際的程式設計中,往往會出現所需的記憶體空間大小取決於實際要處理的資料多少,而實際要處理的資料數量在程式設計時無法確定的情況。如果總是定義一個儘可能大的陣列,又會造成空間浪費。何況,這個“盡可能大”到底應該多大才夠呢?

為了解決上述問題,C++ 提供了一種“動態記憶體分配”機制,使得程式可以在執行期間,根據實際需要,要求作業系統臨時分配一片記憶體空間用於存放資料。此種記憶體分配是在程式執行中進行的,而不是在編譯時就確定的,因此稱為“動態記憶體分配”。

在 C++ 中,通過 new 運算子來實現動態記憶體分配。new 運算子的第一種用法如下:

T *p = new T;

其中,T 是任意型別名,p 是型別為 T* 的指標。

這樣的語句會動態分配出一片大小為 sizeof(T) 位元組的記憶體空間,並且將該記憶體空間的起始地址賦值給 p。例如:
int* p;
p = new int;
*p = 5;
第二行動態分配了一片 4 個位元組大小的記憶體空間,而 p 指向這片空間。通過 p 可以讀寫該記憶體空間。

new 運算子還有第二種用法,用來動態分配一個任意大小的陣列:

T *p =new T[N];

其中,T 是任意型別名,p 是型別為 T* 的指標,N 代表“元素個數”,可以是任何值為正整數的表示式,表示式中可以包含變數、函數呼叫等。這樣的語句動態分配出 N × sizeof(T) 個位元組的記憶體空間,這片空間的起始地址被賦值給 p。例如:
int* pn;
int i = 5 ;
pn = new int[i*20];
pn[0] = 20 ;
pn[100] = 30;
最後一行編譯時沒有問題,但執行時會導致陣列越界。因為上面動態分配的陣列只有 100 個元素,pn[100] 已經不在動態分配的這片記憶體區域之內了。

如果要求分配的空間太大,作業系統找不到足夠的記憶體來滿足,那麼動態記憶體分配就會失敗,此時程式會拋出異常。關於這一點,將在後續章節中介紹。

程式從作業系統動態分配所得的記憶體空間在使用完後應該釋放,交還作業系統,以便作業系統將這片記憶體空間分配給其他程式使用。C++ 提供 delete 運算子,用以釋放動態分配的記憶體空間。delete 運算子的基本用法如下:

delete p;

p 是指向動態分配的記憶體的指標。p 必須指向動態分配的記憶體空間,否則執行時很可能會出錯。例如:
int* p = new int;
*p = 5;
delete p;
delete p;  //本句會導致程式出錯
上面的第一條 delete 語句正確地釋放了動態分配的 4 個位元組記憶體空間。第二條 delete 語句會導致程式出錯,因為 p 所指向的空間已經釋放,p 不再是指向動態分配的記憶體空間的指標了。

如果是用 new 的第二種用法分配的記憶體空間,即動態分配了一個陣列,那麼釋放該陣列時,應以如下形式使用 delete 運算子:

delete[] p;

p 依然是指向動態分配的記憶體的指標。例如:
int* p = new int[20];
p[0] = 1;
delete[] p;
同樣地,要求被釋放的指標 p 必須是指向動態分配的記憶體空間的指標,否則會出錯。

如果動態分配了一個陣列,但是卻用delete p的方式釋放,沒有用[],則編譯時沒有問題,執行時也一般不會發生錯誤,但實際上會導致動態分配的陣列沒有被完全釋放。

牢記,用 new 運算子動態分配的記憶體空間,一定要用 delete 運算子釋放。否則,即便程式執行結束,這部分記憶體空間仍然不會被作業系統收回,從而成為被白白浪費掉的記憶體垃圾。這種現象也稱為“記憶體洩露”。

如果一個程式不停地進行動態記憶體分配而總是沒有釋放,那麼可用記憶體就會被該程式大量消耗,即便該程式結束也不能恢復。這就會導致作業系統執行速度變慢,甚至無法再啟動新的程式。但是,只要重新啟動計算機,這種情況就會消失。

程式設計時如果進行了動態記憶體分配,那麼一定要確保其後的每一條執行路徑都能釋放它。

另外還要注意,釋放一個指標,並不會使該指標的值變為 NULL。