在 C++ 中如果使用 new
配置陣列, 釋放記憶體時就必須使用 delete[]
, 你可能會覺得奇怪, 為什麼不單獨使用 delete
就好, 還要區分 delete
和 delete[]
?
表面的理由--編譯器無法分辨是不是陣列
之所以要區分 delete
和 delete[]
, 有一個表面上的理由, 就是編譯器無法分辨要釋放的是單一個物件還是陣列?舉例來說:
#include<iostream>
using namespace std;
int main()
{
int *p1 = new int;
int *p2 = new int[10];
return 0;
}
對於編譯器來說, p1
和 p2
都是指向整數, 並沒有差別, 所以它無法幫你決定該如何釋放記憶體?
不過你可能會有個疑問, 實際上即使是使用 delete[]
, 也一定要有方法知道原先配置的記憶體到底有多大, 才能正確釋放整個陣列吧?
實際上可以在執行時期分辨是否為陣列
沒錯, 每塊配置的記憶體除了所要求的大小以外, 還會額外配置一塊固定大小, 用來記錄實際配置記憶體大小等等相關資訊的記憶體, 通常會放置在new
傳回的位址前面。既然有這樣的資訊, 那為何不直接就把整塊記憶體釋放就好了, 何必還需要 delete[]
?
這是因為在釋放記憶體之前, 必須先叫用陣列中個別物件的解構器 (destructor), 例如:
#include<iostream>
using namespace std;
class Foo {
public:
~Foo() {
cout << "release me" << endl;
}
};
int main()
{
Foo *p = new Foo[3];
delete[] p;
return 0;
}
漏掉這個步驟, 就可能會導致個別物件佔用的資源沒有釋放, 導致資源流失 (leak)。
C++ 效率優先的核心精神
你可能也會想說, 那也不難啊!釋放記憶體的時候, 只要利用額外記錄的相關資訊, 就可以判斷是單一物件還是陣列了, 像是明明 p2
是指向整數, 但配置的記憶體大小卻是 40 個位元組, 顯然是一個 10 個整數的陣列, 這樣只要使用 delete
就可以應付所有的狀況。
不過實際上 C++ 卻不這樣做, 這是因為 C++ 的核心精神就是效率優先, 沒有必要就不會在執行時期耗費時間。一旦每次 delete
都要在執行時期檢查是否是陣列, 對於不是陣列的資料就會多耗費執行時間。
因此 C++ 的作法是由程式設計師自己負責, 明確告知編譯器這邊要釋放的是陣列, 才會在實際執行時利用記錄資訊計算陣列中物件個數, 並且一一叫用個別物件的解構器 (destructor), 最後再釋放整塊記憶體。如果要釋放的不是陣列, 就只要直接釋放記憶體即可。
其實這樣的核心精神並不少見, 像是類別中的成員函式預設都不是虛擬函式就是同樣的概念, 只有在類別中有虛擬函式的情況下, 才會建立虛擬函式表, 並在執行時期以查表的方式叫用真正的成員函式。如果類別中沒有虛擬函式, 那麼成員函式實際上就跟一般函式沒有什麼兩樣, 只是產生的程式碼會在叫用函式時多傳入指向物件本身的指位器而已。
Top comments (0)