C++記憶體管理:簡易記憶體池的實現

 什麼是記憶體池?

在上一篇 C++記憶體管理:new / delete 和 cookie 中談到,頻繁的調用 malloc 會影響運行效率以及產生額外的 cookie, 而記憶體池的思想是預先申請一大塊記憶體,當有記憶體申請需求時,從記憶體池中取出一塊記憶體分配給目標對象。

它的實現過程為:

  1. 預先申請 chunk 大小的記憶體池, 將記憶體池劃按照對象大小劃分成多個記憶體塊。
  2. 以鏈表的形式,即通過指針將記憶體塊相連,頭指針指向第一個空閑塊。
  3. 當有記憶體申請需求時,首先檢查頭指針是否指向空閑塊,如果是則將頭指針指向的第一個空閑塊分配出去(從鏈表移除),同時頭指針指向下一個空閑塊;若頭指針為空,說明當前記憶體池已分配完,需要重新申請新的記憶體池。
  4. 當有記憶體釋放需求時,將釋放的記憶體塊重新加入鏈表的表頭,調整頭指針指向新加入的空閑塊。這也意味著,如果申請了多個記憶體池,在記憶體釋放的過程中會慢慢的合併到一起。

 

初步實現

 

#include <iostream>
using namespace std;


class Screen {
public:
	Screen(int x) : i(x) { };
	int get() { return i; }

	void* operator new(size_t);
	void  operator delete(void*, size_t);

private:
	Screen* next;
	static Screen* freeStore;  //頭指針
	static const int screenChunk;  //記憶體塊數量
private:
	int i;
};

Screen* Screen::freeStore = 0;
const int Screen::screenChunk = 5;

void* Screen::operator new(size_t size){
	Screen *p;
	if (!freeStore) {  //記憶體池是空的
		size_t chunk = screenChunk * size;
		freeStore = p = reinterpret_cast<Screen*>(new char[chunk]);
		for (; p != &freeStore[screenChunk - 1]; ++p) {  //以鏈表的形式串聯起來
			p->next = p + 1;
		}
		p->next = 0;
	}
	p = freeStore;
	freeStore = freeStore->next;
	return p;
}

void Screen::operator delete(void *p, size_t){
	//將記憶體塊重新加入鏈表表頭,同時調整頭指針
	(static_cast<Screen*>(p))->next = freeStore;
	freeStore = static_cast<Screen*>(p);
}

//-------------
void test(){

	cout << "Size: " << sizeof(Screen) << endl;	

	size_t const N = 100;
	Screen* p[N];

	for (int i = 0; i < N; ++i)
		p[i] = new Screen(i);

	for (int i = 0; i < 10; ++i)  //輸出地址觀察
		cout << i << ": " << p[i] << endl;

	for (int i = 0; i < N; ++i)
		delete p[i];
}

int main(){
	test();
	return 0;
}

在上面的程式碼中設置一個記憶體池為5個記憶體塊,當我們進行100次記憶體申請後,列印出前10個地址查看,可以看到前5個地址是連續的,後5個也是連續的,但中間由於重新申請了記憶體池,所以不是連續的。

但是這樣的方法還存在著問題,那就是引入了額外的指針記憶體消耗,接下來將使用embeded pointer進行改進。

 

 

使用嵌入指針改進

上面就使用到了嵌入指針,一個 AirplaneRep 對象的大小為 8 位元組,而一個 Airplane 的指針大小為 4 位元組或 8 位元組。在 32 位機器下, 指針可以借用 AirplaneRep 對象所佔的 8 位元組記憶體空間中的前 4 個位元組,用來連接空閑的記憶體塊。而當記憶體塊需要被分配給對象時,此時它已從鏈表中移除,也就不需要指針來連接了。此時的 8 位元組記憶體空間由 AirplaneRep 佔據。當記憶體釋放時也是同理,由於 Rep 和 next 不會同時用到,所以 embeded pointer 的做法可以減少記憶體消耗。

 

 

 

 

 參考:

  1. 【C++記憶體管理】記憶體管理實例 (二) —— Embeded pointer

  2. 嵌入式指針embedded pointer的概念以及用法

 

Tags: