- 作者: 雪山肥鱼
- 时间:20210920 11:20
- 目的: 浅谈内存池
频繁的new 和 malloc 会造成内存的碎片化。也会造成一定的内存浪费。
可参考5.2节,底层机制
https://www.jianshu.com/p/87ddf519b0c1
针对一个类 实现内存池
核心: 类内重载 operator new,起手一大片内存,是类A大小的整数倍
有些内核中的 关于内存管理 slab 机制的 味道


大块大块内存串起来
用static 变量 来管理 整块内存,因为static是跟着类走的,与对象无关,可以起到ob的作用。
一个全局 ob 指针 m_FreePosi 告知,下一个可用空间在哪里。
但每一个小块,即对象,需要知道分界线在哪里,所以用拥有自己的next
class A
{
public:
static void * operator new(size_t size);
static void operator delete(void *phead);
static int m_iCount;// 分配计数统计,每new一次,统计一次
static int m_iMallocCount; //分配计数统计,每malloc一次,统计一次
private:
A * next; //找分界点。
static A * m_FreePos;//总是指向一块可以分配出去的内存首地址
static int m_sTrunckCount;//一次分配几个块的内存
}
int A::m_iCount = 0;
int A::m_iMallocCount = 0;
A* A::m_FreePos = nullptr;
int A::m_sTrunkCount = 5;//一次分配5块
void * A::operator new(size_t size)
{
A *tmplink;
if(m_FreePos == nullptr)
{
size_t realsize = m_sTrunckCount * size;
m_FreePos = reinterpret_cast<A*>(new char[realsize]);
tmplink = m_FreePos;
for(; tmplink!=&m_FreePos[m_sTrunckCount-1]; ++tmplink)
{
tmplink->next = tmplink+1;
}
tmplink->next = nullptr;//最后一个 指向null
++m_iMallocCount;//统计
}
tmplink = m_FreePos;
m_FreePos = m_FreePos->next;//下一个可用的m_FreePos,也就是new后把第一个返回出来表示正在使用
++m_iCount;
return tmplink;
}
析构:(非完全体),析构哪一个,表示哪一个是m_FreePos,即下一块可用

void A::operator delete(void *phead)
{
(static_cast<A*>(phead))->next = m_FreePos;
m_FreePos = static_cast<A*>(phead);
}
实际应用:
void func()
{
for(int i =0; i<500;++i)
{
A*pa = new A();
}
}
//消耗时间明显小于 裸new
只要内存池不被耗光,就不用重新new
嵌入式指针的改进
上述代码中 普通成员变量next 的作用,实际起到了定位下一个可用块的位置。找到后,丢给m_FreePos 即可。独自占4个字节,有些不必要。所以利用嵌入式指针。
何为嵌入式指针
class TestEP
{
public:
int m_i;
int m_j;
public:
struct obj
{
struct obj *next;//嵌入式指针
}
}
void func()
{
TestEP mytest;
cout << sizeof(TestEP) << endl;//8
cout << sizeof(mytest) << endl;//8
cout << sizeof(TestEP::obj) << endl;
TestEP::obj *ptemp;//定义一个指针
ptemp = (TestEP::obj*)&mytest;//把对象mytest 首地址给了这个指针,ptemp,
ptemp->next = nullptr;
}
上述结构的嵌入式指针,并不占对象空间大小。理论上就是在类对象的前4个字节(指针大小为4个字节)安插指针变量,待使用完毕后,归还给对象即可。
也就是说对于空闲块,这4个字节的指针值才有意义,已经分出去的块,这4个字节已经被对象占满了。
核心思想:返回obj 指针就完事儿了。内存已经开好了,不用另开

//单独为内存池写一个类
class MyAllocator
{
public:
void allocate(size_t size)
{
obj * tmplink;
if(m_FreePos == nullptr)
{
size_t realsize = m_sTruckCount*size;
m_FreePos = (obj*)malloc(realsize);
tmplink = m_FreePos;
for(int i =0; i< m_sTruckCount -1; i++)
{
tmplink->next = (obj*)((char*)tmplink+size);
tmplink = tmplink->next;
}
tmplink = m_FreePos;
m_FreePos = m_FreePos->next;
return tmplink;
}
}
void deallocate(void *phead)
{
((obj*)head) -> next = m_FreePos;
m_FreePos = (obj*)phead;
}
private:
struct obj
{
struct obj* next;
}
int m_sTruckCount 5;
obj *m_FreePos = nullpt
};
class A
{
public:
int m_i;
int m_j;
public:
static MyAllocator myallocate;
static void * operator new(size_t size)
{
return myallocate.allocate(size);
}
static void operator delete(void *phead0
{
return myallocate.deallocate(phead);
}
};
MyAllocate A::myallocate;
void func()
{
A *mypa[100];
for(int i = 0; i< 15; ++i)
{
mypa[i] = new A();//用完了 自己会分配
printf("%p\n", mypa[i]);
}
for(int i = o; i< 15;++i)
{
delete mypa[i]
}
}
缺陷: 没有真正的析构掉内存
网友评论