內存管理無外乎希望提高兩方面性能:
- 提高運行效率。可以通過減少 malloc 函數調用的次數來提高運行效率。
- 提高空間利用率。減少浪費。每次通過 new 分配的內存塊上下兩個部分都帶有 cookie,記錄着這塊內存的大小等相關信息。可以通過分配一大塊內存池,在將其中分爲一小塊一小塊以供對象實例存放,這樣的話,只在這一大塊的頭尾存在 cookie,中間部分省掉了很多 cookie 的佔用內存,提高空間利用率。
下面是一個簡單的內存管理的例子:
class Screen
{
public:
Screen(int x) : i(x) {}
int get() { return i; }
void* operator new(size_t size)
{
Screen* p;
//第一次時,分配一大塊內存
if (!freeStore)
{
//需要分配的內存大小
size_t chunk = screenChunk * size;
//分配一塊內存,reinterpret_cast 將 char* 轉型爲 screen*
freeStore = reinterpret_cast<Screen*>(new char[chunk]);
p = freeStore;
//將這一塊內存串成鏈表
for (; p != &freeStore[screenChunk - 1]; ++p)
{
p->next = p + 1;
}
p->next = nullptr;
}
//保存當前內存塊地址
p = freeStore;
//指向下一塊內存
freeStore = freeStore->next;
return p;
}
void operator delete(void* pdead)
{
//直接將釋放的內存插入到鏈表頭部
static_cast<Screen*>(pdead)->next = freeStore;
freeStore = static_cast<Screen*>(pdead);
}
private:
Screen *next;
//指向當前可用的待分配內存
static Screen* freeStore;
//一次分配的內存池的大小
static const int screenChunk;
private:
int i;
};
const int Screen::screenChunk = 24;
Screen* Screen::freeStore = nullptr;
int main()
{
cout << sizeof(Screen) << endl;
const int N = 10;
Screen* p[N];
for (int i = 0; i < N; ++i)
p[i] = new Screen(i);
for (int i = 0; i < N; ++i)
cout << p[i] << endl;
for (int i = 0; i < N; ++i)
delete p[i];
}
輸出結果如下:
可以看到,每個 screen 指針都間隔 8 個字節,剛好等於 screen 的大小,證明 object 都在內存池中確實是緊密的排列在一起。
如果採用全局的 operator new ,輸出的結果如下:
分配的內存地址不連續。(由於每一塊內存中都包含 cookie 數據)
注意,由於爲了將內存池中的內存串成鏈表,Screen 類中新增了一個指針數據,這個數據的增加反過來又增加了內存消耗。所以,應該權衡這一個內存消耗和由於 cookie 數據的減少而節省下的來的內存。
總的來說,這種內存管理方式由於新增的指針的存在,所以它的設計並不算好,只是一個初步的方案。後面的改進方案可以消除這一個指針。
有幾個疑問說一說:
1.是最多隻能分配 screenChunk 個實例內存嗎?
並不是,screenChunk 只是每次新分配時的內存單元大小。分配的內存單元被串成鏈表,鏈表的尾部指向 null。當客戶端申請的實例動態內存個數超過 screenChunk 時,freeStore 再一次變成空指針,於是會再次申請新的內存。
2.那多次分配的內存單元豈不是互相獨立,沒有鏈接在一起了?
也並不是。各個分配的內存單元會在 operator delete 函數中被串起來。因爲每次客戶端歸還內存時,該小塊內存會被插入到當前頭指針 freeStore 的前面,由此實現鏈接。
接下來的這篇會將如何利用 Embeded pointer 的概念消除 next 指針佔用的內存。