先理解分配器原理->内存分配,再看源码分析。
一级分配器
当二级分配器分配内存失败时再调用一级分配器,4.9版本后去掉一级分配器,本文章主要分析二级分配器的内存池机制。
//一级分配器
template <int inst>
class __malloc_alloc_template {
private:
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif
public:
static void * allocate(size_t n)
{
void *result = malloc(n);
if (0 == result) result = oom_malloc(n);
return result;
}
static void deallocate(void *p, size_t /* n */)
{
free(p);
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz);
if (0 == result) result = oom_realloc(p, new_sz);
return result;
}
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
//分析内存分配函数,没什么机制,就是单纯调用malloc
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void (* my_malloc_handler)(); //函数指针
void *result;
for (;;) { //循环,直到开辟内存空间成功
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)();
result = malloc(n); //调用malloc开辟空间
if (result) return(result); //返回地址指针
}
}
simple_alloc
换肤工程,其实就是对alloc的简单包装,容器中会使用这个间接调用alloc
//容器内部会有这个声明
template <class T, class Alloc = alloc>
class vector {
//.....
//.....
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
}
//容器中函数的调用方式:data_allocator::allocate
template<class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n)
{ return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
static T *allocate(void)
{ return (T*) Alloc::allocate(sizeof (T)); }
static void deallocate(T *p, size_t n)
{ if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
static void deallocate(T *p)
{ Alloc::deallocate(p, sizeof (T)); }
};
二级分配器alloc(主要分析)
首先记几个名称的type,
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef __default_alloc_template<false, 0> single_client_alloc;
复习一下内存分配的图
二级分配器__default_alloc_template,具体功能见代码注释
template <bool threads, int inst>
class __default_alloc_template {
private:
enum {__ALIGN = 8}; //上调边界,每个区号多占8字节,#0对应8,#1对应16
enum {__MAX_BYTES = 128}; //最大单个快内存128个字节
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; //区块上限:123/8=16,意思是#0到#15共16个区号
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
//字节对齐算法,8字节对齐,原理很简单,7取反0x11111000,
//每次&操作置最低三位为0,自然满足8字节对齐
}
__PRIVATE:
union obj {
union obj * free_list_link;
//嵌入式指针,分配时这个指针指向下一个分配块,称为free_list_link
//此指针和数据共享内存,当有数据写入时自动被覆盖
char client_data[1]; /* The client sees this. 这个其实可以拿掉 */
};
private:
# ifdef __SUNPRO_CC
static obj * __VOLATILE free_list[]; //默认值,不用在意
// Specifying a size results in duplicate def for 4.1
# else
static obj * __VOLATILE free_list[__NFREELISTS]; //分配16个区号,即链表#0到#15
# endif
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
//向上取整算法,决定bytes的空间由哪个区号链表负责
//栗子:(57+7)/8-1=7,57字节空间由#7区号链表负责分配,因为#7号链表上每一块为64字节。
}
// Returns an object of size n, and optionally adds to size n free list.
//返回大小为n的对象,并可选地添加到大小为n的空闲列表中。
static void *refill(size_t n);
// Allocates a chunk for nobjs of size "size". nobjs may be reduced
// if it is inconvenient to allocate the requested number.
//为大小为“size”的nobjs分配一个块。nobjs可能会减少
//如果分配所要求的号码不方便。返回大小为n的对象,并可选地添加到大小为n的空闲列表中。
static char *chunk_alloc(size_t size, int &nobjs);//分配内存块操作
// Chunk allocation state.
static char *start_free; //指向备用池的头
static char *end_free; //指向备用池的尾
static size_t heap_size; //分配空间总字节数
/*下面涉及到多线程,不作分析,跳到allocate函数进行分析*/
# ifdef __STL_SGI_THREADS
static volatile unsigned long __node_allocator_lock;
static void __lock(volatile unsigned long *);
static inline void __unlock(volatile unsigned long *);
# endif
# ifdef __STL_PTHREADS
static pthread_mutex_t __node_allocator_lock;
# endif
# ifdef __STL_WIN32THREADS
static CRITICAL_SECTION __node_allocator_lock;
static bool __node_allocator_lock_initialized;
public:
__default_alloc_template() {
// This assumes the first constructor is called before threads
// are started.
if (!__node_allocator_lock_initialized) {
InitializeCriticalSection(&__node_allocator_lock);
__node_allocator_lock_initialized = true;
}
}
private:
# endif
class lock {
public:
lock() { __NODE_ALLOCATOR_LOCK; }
~lock() { __NODE_ALLOCATOR_UNLOCK; }
};
friend class lock;
/*以上涉及多线程*/
public:
/* n must be > 0 */
static void * allocate(size_t n) ///分配内存函数
{
//见下面分析
};
/* p may not be 0 */
static void deallocate(void *p, size_t n) //回收内存函数,注意此处并没有还给系统
{
//见下面分析
}
static void * reallocate(void *p, size_t old_sz, size_t new_sz);
} ;
allocate函数
/* n must be > 0 */
static void * allocate(size_t n) ///分配内存函数
{
//有定义 #define __VOLATILE volatile, volatile涉及多线程,也可以拿掉看待
obj * __VOLATILE * my_free_list; // obj ** my_free_list ,意为指向区号链表的指针
obj * __RESTRICT result;
if (n > (size_t) __MAX_BYTES) {
return(malloc_alloc::allocate(n));
//如果申请内存大于128字节,那么交给第一级分配器处理
}
my_free_list = free_list + FREELIST_INDEX(n);
/*1.free_list指向区块链表的头部,即#0位置
2.FREELIST_INDEX(n)分析见上面,计算出由哪块区号链表负责这块内存
3.my_free_list 最后指向那块区号链表位置。
*/
// Acquire the lock here with a constructor call.
// This ensures that it is released in exit or during stack
// unwinding.
# ifndef _NOTHREADS
/*REFERENCED*/
lock lock_instance; //线程加锁
# endif
result = *my_free_list;
//*my_free_list取得相应区号链表中的值,其值也是个地址,指向这个区号链表负责的内存。
//result此时指向这块内存的头部
if (result == 0) { //如果指向的内存没有分配空间的话,执行分配空间操作
void *r = refill(ROUND_UP(n)); //填充free_list,内部调用malloc,返回区块的起始地址,
//详细分析见alloc源码分析 sgi-2.91版本 (二)。
//ROUND_UP函数8字节对齐
return r;
}
*my_free_list = result -> free_list_link;
//free_list_link指向下一块可用内存
//此时result会返回给容器push数据,*my_free_list也就是区号链表中的地址改为指向下一块可用内存,为下一次容器push做准备。
return (result);
};
结合图理解,图来自侯捷老师
deallocate函数
/* p may not be 0 */
static void deallocate(void *p, size_t n) //回收内存函数,注意此处并没有还给系统
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;
if (n > (size_t) __MAX_BYTES) {
malloc_alloc::deallocate(p, n); //改用第一级分配器
return;
}
my_free_list = free_list + FREELIST_INDEX(n);
//my_free_list 指向相应区号链表
// acquire lock
# ifndef _NOTHREADS
/*REFERENCED*/
lock lock_instance; //多线程加锁
# endif /* _NOTHREADS */
q -> free_list_link = *my_free_list;
//*my_free_list取区号链表存储的地址,是一个指向该区号负责的内存中可用空间的地址
//q -> free_list_link赋值,因此q内存指向的下一个区域是一块可用空间
*my_free_list = q;
//此时区号链表中存储的地址变为q
//下一刻容器push时就会覆盖本次处理中q指向的原来数据的内存,
//此时可以理解这个函数的回收本质就是覆盖,你可以使用那些你标记为回收成功的空间。
//free_list_link的存在可以保证分配空间中的未用空间不会丢失地址。
// lock is released here
}
关于回收函数这个图,存疑,侯捷老师认为是头插法,但上面实际的代码分析并不是,
my_free_list = free_list + FREELIST_INDEX(n);
*my_free_list取得地址是指向一块可用空间,并不指向内存块头部 。