很久没看boost源码了,今天又拿来看了看,这次看了boost中一个内存池的实现,感觉很不错,但似乎里面重复代码多了一点: )
先来看一个辅助结构,这个辅助结构用模板元编程实现获得最小公倍数和最大公约数
求最大公约数时用的是欧几里得算法(这里为了让这个模板更容易看懂,做了一些简化工作).
template <unsigned A, unsigned B, bool Bis0>
struct ct_gcd_helper;
template <unsigned A, unsigned B>
struct ct_gcd_helper<A, B, false>
{
static const unsigned A_mod_B_ = A % B;
static const unsigned value = ct_gcd_helper<B, static_cast<unsigned>(A_mod_B_),
(A_mod_B_ == 0)>::value;
};
template <unsigned A, unsigned B>
struct ct_gcd_helper<A, B, true>
{
static const unsigned value = A;
};
这里用来模板偏特化,如果条件为真,也即A_mod_B == 0,则value = A,否则一直这样取模下去
//计算最大公约数
template <unsigned A, unsigned B>
struct ct_gcd
{
BOOST_STATIC_ASSERT(A != 0 && B != 0);
static const unsigned value = ct_gcd_helper<A, B, false>::value;
};
//计算最小公倍数
template <unsigned A, unsigned B>
struct ct_lcm
{
static const unsigned value =
A / ct_gcd<A, B>::value * B);
//value为A除以最大公约数乘以B
};
此外还提供了两个用迭代实现得求最小公倍数和最大公约数的函数
template <typename Integer>
Integer gcd(Integer A, Integer B)
{
do
{
const Integer tmp(B);
B = A % B;
A = tmp;
} while (B != 0);
return A;
}
template <typename Integer>
Integer lcm(const Integer & A, const Integer & B)
{
Integer ret = A;
ret /= gcd(A, B);
ret *= B;
return ret;
}
在看下一个辅助结构之前,看看这个结构处理的内存块的形式
http://hi.baidu.com/lyskyly/album/item/5c4a2cf35d4fe951342acc70.html
图一
这是pool过一会儿分配的内存块,上面蓝色小格为请求的内存块大小经过适当处理后的大小,这样的pool构造时小块默认是32个,以后每分配一个大块内存,就让小块内存加倍,你可以在pool构造函数的第二个参数指定.最后划分出两个小块,第一个小块用来存放下一个这大内存块的指针,这样这些大内存块就可以串起来形成一个链表.
第二个内存块存放下一个块的大小.
然后看这个辅助结构
template <typename SizeType>
class PODptr
{
public:
typedef SizeType size_type;
private:
char * ptr;
size_type sz;
char * ptr_next_size() const
{ return (ptr + sz - sizeof(size_type)); }
char * ptr_next_ptr() const
{
return (ptr_next_size() -
pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value);
}
public:
PODptr(char * const nptr, const size_type nsize)
:ptr(nptr), sz(nsize) { }
PODptr()
:ptr(0), sz(0) { }
bool valid() const { return (begin() != 0); }
void invalidate() { begin() = 0; }
char * & begin() { return ptr; }
char * begin() const { return ptr; }
char * end() const { return ptr_next_ptr(); }
size_type total_size() const { return sz; }
size_type element_size() const
{
return (sz - sizeof(size_type) -
pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value);
}
size_type & next_size() const
{ return *(reinterpret_cast<size_type *>(ptr_next_size())); }
char * & next_ptr() const
{ return *(reinterpret_cast<char **>(ptr_next_ptr())); }
PODptr next() const
{ return PODptr<size_type>(next_ptr(), next_size()); }
void next(const PODptr & arg) const
{
next_ptr() = arg.begin();
next_size() = arg.total_size();
}
};
在构造函数中初始化了ptr和sz,ptr是指向这块内存头的一个char*型指针,sz是这块内存的总大小.
char * ptr_next_size() const
{ return (ptr + sz - sizeof(size_type)); }
这个成员函数是用来定位存放下一个块的大小的那个小块的(第二个小块),它让ptr + sz这样移动到大块的末尾,然后再减去sizeof(size_type),这是这个小块的大小,这样就定位到这个小块了.
同理
char * ptr_next_ptr() const
{
return (ptr_next_size() -
pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value);
}
用来定位到指向下一个大块的指针那个小块.(第一个白色小块)
char * & begin() { return ptr; }
char * begin() const { return ptr; }
begin()用来返回ptr的值或它的引用
bool valid() const { return (begin() != 0); }
返回ptr是否为0,如果不为0,就是可用的.
void invalidate() { begin() = 0; }
让ptr = 0,让这这个对象不可用, begin() = 0;这种形式是因为此时返回的是ptr的引用
char * end() const { return ptr_next_ptr(); }
返回指向下一个大块的指针
size_type element_size() const
{
return (sz - sizeof(size_type) -
pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value);
}
返回除去后面两个薄记的小块后的大小
size_type & next_size() const
{ return *(reinterpret_cast<size_type *>(ptr_next_size())); }
返回下一个大块的大小,这里先把第二个小块转化为size_type(通常情况下是size_t类型)的指针,然后再取值.这里返回的也是一个引用
char * & next_ptr() const
{ return *(reinterpret_cast<char **>(ptr_next_ptr())); }
先说说这种指针的用法
char a[] = {10,0,0,0};
char * p = a;
char ** pp = reinterpret_cast<char**>(p);
char * pother = *pp;
cout<<reinterpret_cast<int>(pother)<<endl;
这里将会输出10,也就是把p指向的内存块开始的连续四个字节存放的值,赋值给pother.这里reinterpret_cast<char**>(p);先将p转化为char**,那么p所指向的内存块中存放的值就是一个char*类型的指针.
在这个结构里ptr_next_size();返回指向最后两个小块的第一个内存块的指针,这个内存块存放就是指向下一个大块的指针的值, return *(reinterpret_cast<char **>(ptr_next_ptr()));将那个值转化为一char*的指针返回,那个返回的指针指向的是下一个大块的第一个字节
PODptr next() const
{ return PODptr<size_type>(next_ptr(), next_size()); }
返回一个包装下一个大块的PODPtr结构.
void next(const PODptr & arg) const
{
next_ptr() = arg.begin();
next_size() = arg.total_size();
}
用来填充最后面的两个小块,这样可以方便把它串成一个链表
PODPtr分析完了,它起的只是一个辅助作用,它针对一块特定形式的内存,提供一些成员函数,方便把这些大块内存串接起来
下面再看一个辅助结构,这个结构很重要,在看之前,也先看看这个结构怎么管理分配的内存
http://hi.baidu.com/lyskyly/album/item/bfb1a4fb02df6d234e4aea71.html
图二
上面的图表示这个大内存块被多次分配和释放后的情况,其中绿色小块,表示已经分配出去了的内存,蓝色表示还没有被分配出去的空闲内存块.在绿色小块中存了东西,它存放了指向下一个空闲小块的指针.这样,所有空闲小块就被串了起来.
template <typename SizeType>
class simple_segregated_storage
{
public:
typedef SizeType size_type;
private:
simple_segregated_storage(const simple_segregated_storage &);
void operator=(const simple_segregated_storage &);
static void * try_malloc_n(void * & start, size_type n,size_type partition_size);
protected:
void * first;
void * find_prev(void * ptr);
public:
simple_segregated_storage()
:first(0) { }
static void * segregate(void * block, size_type nsz, size_type npartition_sz,
void * end = 0);
void add_block(void * const block,
const size_type nsz, const size_type npartition_sz);
void add_ordered_block(void * const block,const size_type nsz, const size_type npartition_sz);
bool empty() const { return (first == 0); }
void * malloc();
void free(void * const chunk);
void ordered_free(void * const chunk);
void * malloc_n(size_type n, size_type partition_size);
void free_n(void * const chunks, const size_type n,const size_type partition_size);
void ordered_free_n(void * const chunks, const size_type n, const size_type partition_size);
};
这个类中,一开始两句是把拷贝构造函数和赋值操作符给禁用了.
先来看这个函数
static void * & nextof(void * const ptr)
{ return *(static_cast<void **>(ptr)); }
和上面的一样,这个函数负责,把ptr所指内存块存放的值作为一个指针返回,和上面一样,所有的空闲小块都是通过这种方式串接起来的,串上最后一个小块存的值当然是0了.这里返回的是指针的引用,这意味这可以通过这个函数 nextof(ptr) = XXXX; 的形式改变ptr所指内存块存放的值.相当于调整这个链表中’next’指针的值.
segregate(void * block, size_type nsz, size_type npartition_sz,void * end = 0);
这个函数负责把串进来的block这样一个大内存块,化分为npartition_sz大小的空闲小块,再把这些小块用上面的方法串起来,最后返回指向这个大块内存的指针.这个函数有第四个参数,end,它让这个链表的最后一个元素指向end,这样可以把多个大块内存的空闲内存块串起来.
再来看看pool的定义
template <typename UserAllocator>
class pool: protected simple_segregated_storage<
typename UserAllocator::size_type>
这上面pool保护继承自simple_segregated_storge.UserAllocator可以选择是用malloc分配还是用new分配内存,如果原意也可以自己定义分配方式.
Pool中有个store()函数, simple_segregated_storage<size_type> & store() { return *this; }
它将pool转换为simple_segregated_storage类型,返回引用.
下面看一段代码
boost::pool<> p(sizeof(int));
int * ptr = static_cast<int*>(p.malloc());
int * ptr2 = static_cast<int*>(p.malloc());
p.malloc的第一次调用
void * malloc()
{
// Look for a non-empty storage
if (!store().empty())
return store().malloc();
return malloc_need_resize();
}
调用这个函数,第一次store().empty()肯定是空的,所以调用malloc_need_resize();来先分配一块内存.
template <typename UserAllocator>
void * pool<UserAllocator>::malloc_need_resize()
{
// No memory in any of our storages; make a new storage,
const size_type partition_size = alloc_size();
const size_type POD_size = next_size * partition_size +
details::pool::ct_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type);
char * const ptr = UserAllocator::malloc(POD_size);
if (ptr == 0)
return 0;
const details::PODptr<size_type> node(ptr, POD_size);
next_size <<= 1;
// initialize it,
store().add_block(node.begin(), node.element_size(), partition_size);
// insert it into the list,
node.next(list);
list = node;
// and return a chunk from it.
return store().malloc();
}
这个函数指定分配的内存块的大小, char * const ptr = UserAllocator::malloc(POD_size);这里进行实质性的内存分配.
然后用PODptr处理,这样可以把大块连接起来node.next(list);来做这个连接
store().add_block(node.begin(), node.element_size(), partition_size);
这里还用到simple_segregated_storage的add_block(node.begin(), node.element_size(), partition_size);函数.
void add_block(void * const block,
const size_type nsz, const size_type npartition_sz)
{
// Segregate this block and merge its free list into the
// free list referred to by "first"
first = segregate(block, nsz, npartition_sz, first);
}
这个函数实际上调用的segregate用来划分这个大块内存
其中还有句next_size <<= 1;,让next_size加倍
然后第二次调用int * ptr2 = static_cast<int*>(p.malloc());
if (!store().empty())
return store().malloc();
这次直接调用的是simple_segregated_storage的malloc
void * malloc()
{
void * const ret = first;
// Increment the "first" pointer to point to the next chunk
first = nextof(first);
return ret;
}
直接把第一个小块内存返回,然后条件frist指针,让它指向下一个空闲块
释放内存的时候用
p.free(ptr);
这里调用
void free(void * const chunk)
{ store().free(chunk); }
这个函数再调用simple_segregated_storage的malloc的free
void free(void * const chunk)
{
nextof(chunk) = first;
first = chunk;
}
这里只是把chunk设为空闲内存的第一块
然后malloc_n,free_n函数是一样的,只是malloc_n一次地址分配连续的n个小内存块
还有一组函数
void * ordered_malloc();
void * ordered_malloc(size_type n);
void ordered_free(void * const chunk);
void ordered_free(void * const chunks, const size_type n);
这一组函数可以让所有空闲内存的按照地址由小到大排列,包括所有大内存块中的空闲内存,这就要求大块内存也要有序排列.下面这个图是上面那组函数管理的内存块情况,它不会出现图二那种,排在大块最后的那个蓝色块指向前面的空闲块.
这个pool就是这样管理内存块的.里面还提供了一些辅助函数用来对PODptr链表和空闲块链表进行管理.
最后两个函数
release_memory
purge_memory用来收回所有分配的内存,防止内存泄漏
http://hi.baidu.com/lyskyly/album/item/a6427806375f0a7a03088172.html
图三
好像写得太多了,前面部分太罗嗦了.