1、为什么需要重写operator new 和operator delete
默认的
operator new
具有很好的通用性,可以分配任意大小的内存块。operator delete
也可以释放任意大小的内存块。为了让operator delete
弄清楚需要释放多大内存,通常会在operator new
分配的内存块里附带一些额外信息,用来指明被分配内存块的大小,所以分配内存的大小实际比存储对象所需内存大一点。就因为这种通用性及灵活性,导致在那些需要动态分配大量小的对象的应用程序中可以改善性能,因此在这种情况下为了效率有必要重写。
2、内存池
概念及实现:
内存池就是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升,特别对于小内存分配的时候可以起到减少内存碎片的作用。
实现思路:先让缺省operator new
分配一些大块的原始内存,每块的大小都足以容纳很多个小内存对象。 小内存对象的内存块就取自这些大的内存块。当前没被使用的内存块被组织成链表(自由链表)以备未来小内存对象使用。当动态分配小对象时候,将某个小块内存从自由链表中移除客户并得到此块内存首地址;当客户删除对象时,小块内存放回自由链表。
区别与内存泄漏:
内存泄漏对应于内存分配后指向内存的指针丢失了,客户再也不能使用这块内存了。此处分配内存全部串联在链表之中,客户需要使用时候全部可以使用,只是暂时没有释放而已,以备后续对象动态分配,所以这个称之为内存的池。
简单实现
#ifndef MEMORYPOOL_H
#define MEMORYPOOL_H
#include<new>
#include<iostream>
class MemoryPool
{
public:
MemoryPool(size_t n);//构造函数,构造自由链表
void *alloc(size_t n);//分配函数,从自由链表调出一个小块
void free(void *__ptr , size_t n);//释放函数,内存块加入自由链表
~MemoryPool();//析构函数
int GetChunks(){
return AvailableChunks;
}
private:
static const int BLOCK_SIZE;//声明常量,初始化节点个数
union obj{//联合体内部成员不可能同时使用
union obj *next;//自由链表使用
char client_data[1];//用户使用
};
union obj *headOfFreeList;
int AvailableChunks = 0;//返回自由链表中可用节点的个数
};
#endif // MEMORYPOOL_H
#include "memorypool.h"
const int MemoryPool::BLOCK_SIZE = 5;//定义常量
MemoryPool::MemoryPool(size_t n)//直接构造自由链表
{
//自由链表为空,分配可以容纳BLOCK_SIZE个的n块
union obj *current_obj , *next_obj;
char *chunk = (char *)malloc(BLOCK_SIZE * n);//分配 BLOCK_SIZE*n 大内存
headOfFreeList = current_obj = next_obj = (union obj *)chunk;//首地址
for(int i = 0 ; i < BLOCK_SIZE - 1 ;i++){//将内存块组成自由链表
next_obj = (union obj *)((char *)next_obj + n);//找出下一个节点地址
current_obj->next = next_obj;//存储下一个节点地址
current_obj = next_obj;//更新当前节点,为下次做准备
}
AvailableChunks = BLOCK_SIZE;
current_obj->next = 0;//尾部节点置为空。
}
void *MemoryPool::alloc(size_t n){//分从自由链表分配一个节点
union obj *result = headOfFreeList;
union obj *current_obj , *next_obj;
if(result){//如果为空,那么就是没有了
headOfFreeList = headOfFreeList->next;
AvailableChunks--;
}
else{//从堆中补充自由链表,注意第一个地址返回
char *chunk = (char *)malloc(BLOCK_SIZE * n);//分配 BLOCK_SIZE*n 大内存
result = (union obj *)chunk;
headOfFreeList = current_obj = next_obj = (union obj *)(chunk+n);//首地址
for(int i = 1 ; i < BLOCK_SIZE - 1 ;i++){//将内存块组成自由链表
next_obj = (union obj *)((char *)next_obj + n);//找出下一个节点地址
current_obj->next = next_obj;//存储下一个节点地址
current_obj = next_obj;//更新当前节点,为下次做准备
}
current_obj->next = 0;//尾部节点置为空。
AvailableChunks = BLOCK_SIZE - 1;
}
return result;//返回
}
void MemoryPool::free(void *__ptr , size_t n){//释放一个节点,加入链表头即可
union obj *p = (union obj *)__ptr;
p->next = headOfFreeList;
headOfFreeList = p;
AvailableChunks++;
}
MemoryPool::~MemoryPool(){
}
#include "memorypool.h"
#include<iostream>
using std::cout;
using std::endl;
class Airplane{
public:
Airplane():AirplaneRep(4){}
static void *operator new(size_t size);
static void operator delete(void *p , size_t size);
int val(){
return AirplaneRep;
}
static int Chunks(){
return memPool.GetChunks();
}
private:
int AirplaneRep,i ,j, k;
static MemoryPool memPool;//声明一个静态对象
};
void *Airplane::operator new(size_t size){
//cout << "operator new" << endl;
return memPool.alloc(size);
}
void Airplane::operator delete(void *p , size_t size){
memPool.free(p , size);
}
MemoryPool Airplane::memPool(sizeof(Airplane));//定义并通过构造初始化
int main(void)
{
int i = 9;
Airplane *ptr = new Airplane;//分配内存并调用默认构造
cout << Airplane::Chunks() << endl;//返回可用chunks(内存块)
Airplane *ptr1 = new Airplane;//分配内存并调用默认构造
cout << Airplane::Chunks() << endl;
Airplane *ptr2 = new Airplane;//分配内存并调用默认构造
cout << Airplane::Chunks() << endl;
Airplane *ptr3 = new Airplane;//分配内存并调用默认构造
cout << Airplane::Chunks() << endl;
Airplane *ptr4 = new Airplane;//分配内存并调用默认构造
cout << Airplane::Chunks() << endl;
Airplane *ptr5 = new Airplane;
cout << Airplane::Chunks() << endl;
delete ptr5;
cout << Airplane::Chunks() << endl;
delete ptr4;
cout << Airplane::Chunks() << endl;
delete ptr3;
cout << Airplane::Chunks() << endl;
return 0;
}
初始化默认小块的个数为5,程序运行首先调用静态成员memPool的构造函数Airplane::memPool(sizeof(Airplane));
初始化自由链表。上图是某次允许时自由链表的构造情况,此处内存池首地址headOfFreeList = 0x018b6c20
且sizeof(Airplane) = 16
,所以内存池的首地址为0x018b6c20,尾地址为0x018b6c6f。并且本主机是64位小端机器,每一个地址变量占用8字节,union obj对象占用8字节。经过自由链表之后,可以清楚的看到第一块上面存放的是第二块的地址,第二块存放第三块的地址,第三块存放第四块的地址,第四块存放第五块的地址,第五块为链表末尾置为0。
注意:阅读STL源代码内存分配的时候,自由链表节点大小分别进行了8字节对齐。因为union obj对象占用8字节,所以一个块至少需要8字节,才可以存放一个union obj *指针用来指向下一个块。所以此处sizeof(Airplane)
至少等于8,所以在Airplane类中多声明了几个int变量,否则会出现malloc报错,因为分配的内存不足以存放union obj指针变量。
调用一次new Airplane
,Chunks数量减少一个;delete Airplane
Chunks数量增加一。特别注意此处默认的BLOCK_SIZE为5比较小,所以Chunks为0继续new Airplane
会重新生成内存池,然后释放先前分配的Airplane对象,此时Chunks的值可能大于默认的BLOCK_SIZE为5,例如上面出现了7。为了避免这种情况发生可以让BLOCK_SIZE稍微大一点,使得Chunks为0的概率小点。
STL源代码利用了自由链表池,产生了16个自由链表,分别对应8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节小内存块。思路和上面的实现完全一样,后期会剖析STL源代码里面的内存分配功能。