内存分配(1) — 空闲链表

内存分配是所有成功的库都要费大量心力去做好的事情,除非是对performance很高的需求,至少我现在在工作中很少需要自己来写内存分配策略。我始终觉得一些经典的库,像STL,Loki,Boost是最好的教材。读书也要讲究方法,读STL这样的库,我现在会逼着自己多问几个为什么,为什么作者要这样设计呢,如果换作我自己,我会怎样设计呢,有什么地方没有考虑周全呢。今天我试图来说说内存分配,希望大家多发表意见。交流才能进步嘛-:)
我们经常会写
CMyObject* pObj = new CMyObject(…);  // 从堆上分配一个对象
其实编译器会产生下面的代码:
CMyObject* pObj;
try{
    void* mem = ::operator new( sizeof(CMyObject) ); // 分配内存
    pObj = static_cast<CMyObject*>(mem);
    pc->CMyObject::CMyObject(…);
}
catch( std::bad_alloc)
{
   // 如果内存分配失败就不执行constructor
}
可见,在C++中简单的new一个对象,实际上是两个操作:1)分配内存,内存大小用sizeof在编译时就计算出来了 2)在分配得到的内存地址上调用构造函数。 我们今天讨论的主要是集中在第一步。
可以把::operator new()看作是malloc,其实这最终会导致一个系统调用,去向系统要资源。在文件I/O一样,要提高读写速度,关键是要减少物理文件I/O的次数(磁盘寻道,从物理磁盘读写),典型的解决方案就是buffer,每次我都多要一点,下次要读得东西如果在buffer里面,就不需要从磁盘拿了,毕竟内存操作要比磁盘操作快太多太多了。那自然就想到了,在内存分配中也一样啊,要减少客户程序实际向系统要内存的次数,我每次要得时候多要一点不就可以了吗。是的,那在C++里面怎么实现呢?
在实现的时候有一点要求,就是分配程序的改动,因该尽最大可能不影响客户程序代码,最好一点影响也没有 .这里我们可以通过重载类成员函数operator new()和operator delete来做:
我的想法是这样的:
在第一次需要分配一个CMyObject对象的时候,我就分配100个sizeof(CMyObject)大小的内存,以后第二次要分配一个CMyObject对象的时候,我就从上次剩下的99个后备中间拿一个出来。为了达到这样的目标,我可以维护一个单向链表,一开始把这100个对象串起来(这需要定义一个成员变量next),还需要一个指针,指向下一个可供使用的对象(freeStore)。
class CMyObject{
public:
        void* operator new(size_t);
        void  operator delete(void*,size_t);
private:
        CMyObject* next;
        static CMyObject* freeStore;  //注意阿,freeStore是静态类成员,给所用CMyObject的实例共享哦
};
CMyObject* CMyObject::freeStore = NULL;
 
void* CMyObject::operator new(size_t size)
{
     CMyObject* p = NULL;
     if(!freeStore){ // 第一次要,就要多一点
          size_t chunk = 100*size; // 说句良心话,要他个100个对系统来说也不算多
          freeStore = reinterpret_cast<CMyObject*>(new char[chunk]);  
          // 然后把内存用next指针串起来,物理上是一段连续内存,逻辑上是一个空闲对象的单向链表
         for(p = freeStore; p != &freeStore[100-1]; ++p) p->next = p+1;
         p->next = NULL;
     }
     // 从库存中拿出第一个,给请求的人
     p = freeStore;
     freeStore = freeStore->next; // 调整freeStore到下一个空闲对象
     return p;    
 }
 
下面我们看看释放:
void CMyObject::operator delete(void* p,size_t)
{
      // 把要delete的对象,放到free链表的头部,并不归还给系统哦!
     (static_cast<CMyObject*>(p)) ->next = freeStore;
     freeStore = static_cast<CMyObject*>(p);
}
整个过程就是这样的,其实可以看成一个逻辑上的stack,先reserve一下stack的大小为100,new的时候pop一个出来,delete的时候push回去。
 
有两个地方要注意了:
1. 这是个欠债不还钱的主哦,问系统拿的东西,不还的。要到程序退出的时候,才由进程负责清理。
2. 每次向系统索要内存时,其实都会都要一点点用于记录这块内存的大小之类的,我猜至少是4个字节,但是现在每个类都多了一个额外的next指针的代价。似乎除了分配速度提高以外,在大小上没有优势。我们马上会谈到如何使用embedded pointer解决这个问题。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值