內存池的成員變量
剛開始分配的時候,內存池裏面只有一個內存塊,這塊內存塊總共擁有unit_count個單元。
如果這塊內存塊不夠用,那麼就要再搞一個新的內存塊,新增的這塊內存塊裏一共有grow_count個單元。
新增的這個內存塊要和原來的內存塊用鏈表連起來。相當於在鏈表的頭部插入。memory_block指針就指向了鏈表裏的第一塊內存塊。
unit_size表示了一個單元擁有幾個字節。
class MemoryPool {
public:
int unit_count;
int grow_count;
int unit_size;
MemoryBlock* memory_block;}
內存塊的成員變量
nsize表示這個內存塊裏有多少個字節。就是單元數*每個單元的字節數得到的。
nfree表示這個內存塊裏有幾個單元是空閒的。
nfirst是第一個空閒的單元,它的編號。編號就是這個內存塊裏第幾個單元。
pNext指針指向下一個內存塊。
address的設計非常巧妙。當申請一塊內存塊的時候,是申請了頭部+身體這麼多的內存。
頭部裏就包含了這個對象的成員變量,大小是sizeof(MemoryBlock)這個類的大小。
身體開始纔是真正存放數據的地方。身體的大小就是nsize咯。
脖子的地方就是身體開始地方。address就是這個脖子,我們要用的不是address這個地址裏存放的東西(*address),而是address這個地址。可以直接通過address來獲取哦。
class MemoryBlock {
public:
int nsize;
int nfree;
int nfirst;
MemoryBlock* pNext;
char address[1];}
每個單元的前兩個字節不簡單哦
每個單元的前兩個字節都存放了下一個空閒單元的編號。
所以內存塊初始化的時候,要讓這兩個字節等於當前編號+1。
下面是內存塊初始化的代碼
MemoryBlock(int unitcount,int unitsize) {
nsize = unitcount * unitsize;
nfree = unitcount;
nfirst = 0;
pNext = NULL;
char *p = address;
for (int i = 0; i < unitcount - 1; i++) {
*(short*)p = i + 1;
p += unitsize;
}
*(short*)p = -1;
}
向內存池申請一個單元的內存
如果內存池裏面還沒有內存塊,那肯定是先分配內存,調用內存塊的構造函數,先搞一塊內存塊咯。
if (memory_block == NULL) {
memory_block = (MemoryBlock*)new(unit_count*(sizeof(MemoryBlock) + unit_size))MemoryBlock(unit_count, unit_size);
assert(memory_block != NULL);
}
有了內存塊以後檢查這個內存塊還有沒有空閒的單元可以使用的,沒有的話去下一塊內存塊看看。
MemoryBlock *p = memory_block;
while (p != NULL and p->nfree == 0) {
p = p->pNext;
}
找了半天都沒找到空閒的怎麼辦呢?只能再申請一塊內存塊了,還要把它插到內存塊鏈表的頭部。這裏用頭插法。
if (p == NULL) {
p = (MemoryBlock*)new(grow_count*(sizeof(MemoryBlock) + unit_size))MemoryBlock(grow_count, unit_size);
assert(p != NULL);
p->pNext = memory_block;
memory_block = p;
}
OK,現在找到這個內存塊了。
內存塊裏的nfirst記錄了第一塊空閒的單元。
我們直接返回它不就行了。
可是這樣的話,下次再想從這塊內存塊裏面搞一個單元怎麼辦呢?
所以還是要做一下善後的工作,把nfirst指向下一塊空閒的單元。
並且這個內存塊的空閒單元數減一。
char* free_address=p->address + p->nfirst*p->nsize;
p->nfirst=*(short*)free_address;
p->nfree--;
return free_address;
釋放一個單元的內存
既然你要釋放,你首先要把你要釋放的這個指針傳給我吧。
我直接釋放行不行呢?
肯定不行咯。
這裏的釋放不是說讓你真的把這個內存單元給free掉。而是把它搞成空閒的狀態。
所以首先找到這個單元所在的內存塊。
MemoryBlock* pb = memory_block;
while (pb != NULL and p < pb->address||p>pb->address+pb->nsize) {
pb = pb->pNext;
}
要是沒找到的話,這個單元就不是內存池分配的。
找到了以後呢。
先算出這個單元在這個內存塊裏編號n.
然後還是頭插法。
把這個單元插到這塊內存塊第一個空閒單元的前面。
具體而言,就是讓這個單元的前兩個字節存放第一個空閒單元的編號。
然後讓第一個空閒單元的編號變成這個單元的編號。
空閒單元的個數也可以加上去了。
if (pb != NULL) {
int n = (int)((char*)p - pb->address) / unit_size;
*(short*)p = pb->nfirst;
pb->nfirst = n;
pb->nfree++;
}
測試
MemoryPool pool(3, 3, 4);
int *p1=(int*)pool.Alloc();
*p1 = 111;
pool.Free(p1);