Benjamin

静以修身,俭以养德,非澹薄无以明志,非宁静无以致远。
随笔 - 397, 文章 - 0, 评论 - 196, 引用 - 0
数据加载中……

内存池和placement new(布局new)

内存分配(器)返回的是一块没有初始化的内存,不返回对象;内存分配(器)也不支持设置成虚指针,因为内存分配是在构造执行后;
1 void* raw = allocate(sizeof(Foo));  // line 1
2  Foo* p = new(raw) Foo();            // line 2 
allocate就是一个简单的内存分配(器),上面的代码使用了placement new来构造对象。
下面一步就是做一个对象的内存分配(器)-------------内存池(类)
 1 class Pool {
 2  public:
 3    void* alloc(size_t nbytes);
 4    void dealloc(void* p);
 5  private:
 6    data members used in your pool object
 7  };
 8  
 9  void* Pool::alloc(size_t nbytes)
10  {
11    your algorithm goes here
12  }
13  
14  void Pool::dealloc(void* p)
15  {
16    your algorithm goes here
17  } 
分配对象使用下面的代码
1  Pool pool;
2  
3  void* raw = pool.alloc(sizeof(Foo));
4  Foo* p = new(raw) Foo(); 
或者更简单的代码
Foo* p = new(pool.alloc(sizeof(Foo))) Foo(); 
这样做的好处是可以使用N个内存池而不是共享一块大内存,可以从内存池中分配而不用释放,一次释放整个内存池;也可以其中一部分做共享内存而不用系统的共享内存,从另一个角度来看,很多系统支持alloca()从stack中分配内存块,当然这些内存块也会自动离开在函数返回时,无须delete;当然也可以使用alloca()为(内存)池分配一个大内存块,而无须调用delete,在这里析构函数仅仅只是(deallocates)释放分配的内存。
下一步就是就是改变分配对象的语法:与其使用new(pool.alloc(sizeof(Foo))) Foo() 倒不如new(pool) Foo(),为此我们需要实现下面的函数:
 inline void* operator new(size_t nbytes, Pool& pool)
 {
   return pool.alloc(nbytes);
 } 
现在看看这个new(pool)Foo(),它会调用operator new,通过sizeof(Foo)和pool两个参量,下面看看如何析构对象和释放内存,根据placement new的析构方法,代码如下:
 void sample(Pool& pool)
 {
   Foo* p = new(pool) Foo();
   
   p->~Foo();        // explicitly call dtor
   pool.dealloc(p);  // explicitly release the memory
 } 
但这样做,会有下面几个问题产生:
问题一:防止内存泄露,在Foo* p = new Foo(),编译器会产生类似下面的代码来应对构造中的异常
 // This is functionally what happens with Foo* p = new Foo()
 
 Foo* p;
 
 // don't catch exceptions thrown by the allocator itself
 void* raw = operator new(sizeof(Foo));
 
 // catch any exceptions thrown by the ctor
 try {
   p = new(raw) Foo();  // call the ctor with raw as this
 }
 catch () {
   // oops, ctor threw an exception
   operator delete(raw);
   throw;  // rethrow the ctor's exception
 } 
问题是在deallocates(去分配)内存时如发生异常,具体的说就是new的参数(调用placement(布局) new)时出现异常,此时编译器不知所措,默认就什么都不做。
 // This is functionally what happens with Foo* p = new(pool) Foo():
 
 void* raw = operator new(sizeof(Foo), pool);
 // the above function simply returns "pool.alloc(sizeof(Foo))"
 
 Foo* p = new(raw) Foo();
 // if the above line "throws", pool.dealloc(raw) is NOT called 
所以编译器要做一些类似于全局new operator的工作,当编译器看到 new(pool) Foo(),它要查找与之想匹配的delete operator,如果找到了,就相当于上面显示
的包含try的代码可以正确执行,所以要写个delete operator,像下面的代码所示(即使第二参数和operator new(size_t, Pool&)中的第二个参数不同,也没有关系,编译器也
不会有什么抱怨,此时它会绕过这个try块)
 void operator delete(void* p, Pool& pool)
 {
   pool.dealloc(p);
 } 
注意:这个operator delete不必一定是inline的,可以是,也可以不是。
destruction(析构)/deallocation(去分配)是两个不同的概念,可现实中经常混淆这两个;另外要记住内存池里的内存和哪个对象匹配,一般我们通过Foo *和
pool* 这两个指针来定位;对于这两个方面可能出现的问题,可以用下面的方案来解决。
这个方案就是使每次分配(对象)都关联一个pool *,然后在替代全局delete opeator,如下所示:
void operator delete(void* p)
 {
   if (p != NULL) {
     Pool* pool = /* somehow get the associated 'Pool*' */;
     if (pool == null)
       free(p);
     else
 void* operator new(size_t nbytes)
 {
   if (nbytes == 0)
     nbytes = 1;  // so all alloc's get a distinct address
   void* raw = malloc(nbytes);
   somehow associate the NULL 'Pool*' with 'raw'
   return raw;
 } 

       pool->dealloc(p);
   }
如果不确信dealloc调用free,可以在全局的new operator中使用malloc(),像下面的代码所示(此短代码没有throw std::bad_alloc()new_handler)
  下面的工作就是用pool*关联每次 allocation(分配),这里我们可以使用STL中的std::map<void*,pool*>,即建立查找的查询表,其keys是分配指针(void*),value是
pool*,这里要注意,在从全局的operator new向map中添加数据,因为如果是NULL,会导致递归(结束一次插入同时又插入新项)。
这里用map这个STL里的容器,在大多数情况是可以接受的。
除了map,还有一种方案就是耗内存但是速度快些。就在在pool*之前加上所有的allocate(分配),如果字节是24,考虑到边界对齐(double\long long),可能需要28或32字节,首先给pool*四个字节,然后返回4或8个字节的(指针),从你开始分配时;此时全局的delete opeator至少可以删除这4或8字节指针而不会出现异常,这是如果pool*是null,那么用free调用pool->dealloc(),由free和pool->dealloc()会有4或8字节的指针到原参p,如果不为空,你就可以决定4字节对齐,代码就要像下面示例的这样:
 void* operator new(size_t nbytes)
 {
   if (nbytes == 0)
     nbytes = 1;                    // so all alloc's get a distinct address
   void* ans = malloc(nbytes + 4);  // overallocate by 4 bytes
   *(Pool**)ans = NULL;             // use NULL in the global new
   return (char*)ans + 4;           // don't let users see the Pool*
 }
 
 void* operator new(size_t nbytes, Pool& pool)
 {
   if (nbytes == 0)
     nbytes = 1;                    // so all alloc's get a distinct address
   void* ans = pool.alloc(nbytes + 4); // overallocate by 4 bytes
   *(Pool**)ans = &pool;            // put the Pool* here
   return (char*)ans + 4;           // don't let users see the Pool*
 }
 
 void operator delete(void* p)
 {
   if (p != NULL) {
     p = (char*)p - 4;              // back off to the Pool*
     Pool* pool = *(Pool**)p;
     if (pool == null)
       free(p);                     // note: 4 bytes left of the original p
     else
       pool->dealloc(p);            // note: 4 bytes left of the original p
   }
 } 
上面的代码并没有处理new operator内存不足的情况,如果我们不改变全局的new operator和delete operator,上面的问题依然存在。

posted on 2010-05-17 22:35 Benjamin 阅读(3557) 评论(2)  编辑 收藏 引用 所属分类: C/C++

评论

# re: 内存池和placement new(布局new)  回复  更多评论   

翻译的 c++-faq-lite 吧?
2010-05-18 08:58 | 飘飘白云

# re: BREW平台上如何通过GPS设备获取经纬度  回复  更多评论   

高手:BREW平台上如何通过GPS设备获取经纬度上的代码
请问保存为什么格式的文件能够在手机上运行啊?感谢
2010-05-20 10:39 | 小雷

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理