C++、stl空间配置器源码实现

在C++中所有STL容器的空间分配其实都是使用的std::allocator,它是可以感知类型的空间分配器,并将空间的申请与对象的构建分离开来。
stl的空间配置器运用了链表+内存池和分级控制的技术。
对象池简易实现

struct __allocator
{
  _Alloc __underlying_alloc;

  typedef size_t    size_type;
  typedef ptrdiff_t difference_type;
  typedef _Tp*       pointer;
  typedef const _Tp* const_pointer;
  typedef _Tp&       reference;
  typedef const _Tp& const_reference;
  typedef _Tp        value_type;

  template <class _Tp1> struct rebind {
    typedef __allocator<_Tp1, _Alloc> other;
  };
//对象的创建
void construct(pointer __p, const _Tp& __val)
{ 
    new(__p) _Tp(__val); //为new定位符
}

construct(对象的创建),定位new表达式,在指定空间上申请对象

对象的销毁有两个版本:

1:接收一个指针,将其释放掉

2:接收一个迭代器范围,将其范围内的对象析构掉

1:void destroy(pointer __p) { __p->~_Tp(); }
2:typedef _Tp        value_type;
//判断该对象类型的析构函数是否无关紧要,如果不重要
//就不需要循环释放一个范围,万一范围很大,得不偿失

以value_type()萃取出迭代器的value type,再利用__type_traits判断该类型是否POD类型

__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
                           const _Tp& __x, __true_type)
{
  return fill_n(__first, __n, __x);
    //判断是否为POD类型,实现---------------------------------
    inline _ForwardIter 
	__uninitialized_fill_n(_ForwardIter __first, _Size __n, const _Tp& __x, _Tp1*)
	{
  		is_POD_type _Is_POD;//POD类型
  		return __uninitialized_fill_n_aux(__first, __n, __x, _Is_POD());
	}
}

走向两个分支,一个递归回去,一个直接释放

__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
                           const _Tp& __x, __true_type)
{
  return fill_n(__first, __n, __x);
}

__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
                           const _Tp& __x, __false_type)
{
  _ForwardIter __cur = __first;
  __STL_TRY {
    for ( ; __n > 0; --__n, ++__cur)
      _Construct(&*__cur, __x);
    return __cur;
  }
  __STL_UNWIND(_Destroy(__first, __cur));//释放函数
}

空间的申请分为一级和二级

_Tp* allocate(size_type __n, const void* = 0) 
{
    return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp))) : 0;
    //类型转换可以忽略不看
    __n != 0 ? (_Alloc::allocate(__n * sizeof(_Tp))) : 0;
    //allocate是申请函数分为2种
  }

当大于等于128kb时,和小于128kb时

1:考虑小块空间过多造成的内存碎片

2:尽可能地减少对malloc的调用,因为会频繁陷入用户态和内核态

3:是一个以空间换时间的策略,跟缓冲区、struct内存对齐一样

4:考虑内存不足的应对策略

一级空间配置器
# ifdef __USE_MALLOC

typedef malloc_alloc alloc;
typedef malloc_alloc single_client_alloc;
# else

编译时,g++ -D,在编译时添加参数的__USE_MALLOC

class __malloc_alloc_template 
{

private:

  static void* _S_oom_malloc(size_t);
  static void* _S_oom_realloc(void*, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
    //此宏可能是GNU_C的标准下
    //可以定义0长度数组,可变参数宏
  static void (* __malloc_alloc_oom_handler)();//一个函数指针
#endif

public:

  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);
    if (0 == __result) __result = _S_oom_malloc(__n);
    return __result;
  }

  static void deallocate(void* __p, size_t /* __n */)
  {
    free(__p);
  }
};

主要函数就是 **static void* allocate(size_t __n)**此函数为内存申请

static void deallocate(void* __p, size_t );//内存释放

allocate()内部实现

两级空间配置器,第一级空间配置器使用类模板malloc_alloc_template ,其底层使用的malloc/free进行空间的申请与释放,二级空间配置器使用类模板,default_alloc_template,其底层申请空间大小有分为两个分支进行。

第一分支是当申请的空间大于128字节的时候,还是走 __malloc_alloc_template ,当申请的空间小于128字节的使用,使用内存池+16个自由链表的结构进行。

二级空间配置器
static void* allocate(size_t __n)
  {
    void* __ret = 0;
	//_MAX_BYTES为128定义的宏
    //当申请内存空间大于128字节时
    if (__n > (size_t) _MAX_BYTES) 
    {
      __ret = malloc_alloc::allocate(__n);//正常申请
    }
    else 
    {
        //_obj是一个二级指针,调用了S_freelist_index(__n);
      _Obj* __STL_VOLATILE* __my_free_list= _S_free_list + _S_freelist_index(__n);
      //偏移3的位置,如下图
      //函数实现是
//-----------------------------------------------------------------------------        
      static  size_t _S_freelist_index(size_t __bytes) 
      	{
          //__bytes是申请空间的大小,定32 ALIGN=8   39/8-1=4-1=3;
        	return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  		}
//-----------------------------------------------------------------------------
     	//S_free_list是一个类的静态成员都初始化为0了
        template <bool __threads, int __inst>
		_Obj* __STL_VOLATILE
		::_S_free_list[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
        //result指向了32这个位置
        _Obj* __RESTRICT __result = *__my_free_list;//
      if (__result == 0)//因为my_free_list都被初始化为0了,所以怎么偏移,都是满足如下条件的
      {
           __ret = _S_refill(_S_round_up(__n));//_S_round_up  (32 + 7) & ~ 7
           static size_t _S_round_up(size_t __bytes) 
        { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
          (32 + 7) & ~ 7
   			0010 0111
		 &  1111 1000
   			0010 0000//功能:向上取整,得到8的整数倍,25-32,最后的结果都是32传出
      }
       
        //_S_refill过于复杂,放到下面
      else 
      {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

在这里插入图片描述

_S_refill函数实现

功能:将申请的内存进行切割,切割成20块,并且将他们链起来存储在链表中


void* _S_refill(size_t __n)
{
    int __nobjs = 20;
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    //_S_chunk_alloc函数将数据组块,见如下
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;

    if (1 == __nobjs) return(__chunk);
	
    __my_free_list = _S_free_list + _S_freelist_index(__n);

    
      __result = (_Obj*)__chunk;
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
      for (__i = 1; ; __i++) {
        __current_obj = __next_obj;
        __next_obj = (_Obj*)((char*)__next_obj + __n);
        if (__nobjs - 1 == __i) {
            __current_obj -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}

_S_chunk_alloc函数

功能:将数据组成块状

数据

union _Obj 
	{
		union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this. */
	};
char* _S_start_free = 0;
char* _S_end_free = 0;
size_t _S_heap_size = 0;
char* _S_chunk_alloc(size_t __size, int& __nobjs)//32  20
{
	char* __result;
    size_t __total_bytes = __size * __nobjs = 32 * 20 = 640;
    size_t __bytes_left = _S_end_free - _S_start_free = 0;
	
	else
	{
		size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4)
		                          = 2 * 640 + 0 = 1280;
								  
		_S_start_free = (char*)malloc(__bytes_to_get) = (char *)malloc(1280);
		_S_heap_size += __bytes_to_get = 1280;
        _S_end_free = _S_start_free + __bytes_to_get;
        //将内存块的前后都给涵盖住,begin(),end()
        return(_S_chunk_alloc(__size, __nobjs));//32,20,再次调用
		
		//递归调用
		
		char* __result;
        size_t __total_bytes = __size * __nobjs = 32 * 20 = 640;
        size_t __bytes_left = _S_end_free - _S_start_free = 1280;
		
		if (__bytes_left >= __total_bytes) 
		{
			__result = _S_start_free;
			_S_start_free += __total_bytes;//从640字节开始
			return(__result);
		}
	}
	
}

空间配置器的释放

如果大于128个字节的申请时,调用malloc_alloc::deallocate(__p, __n);一级配置器释放

static void deallocate(void* __p, size_t __n)//32
  {
    if (__n > (size_t) _MAX_BYTES)//128
      malloc_alloc::deallocate(__p, __n);//free
    //----------------函数实现------------------------------
    static void deallocate(void* __p, size_t)
  		{
    		free(__p);
  		}
    else 
        //二级空间配置器的释放
    {
      _Obj**  __my_free_list = _S_free_list + _S_freelist_index(__n);
        //_S_free_list[3]
      _Obj* __q = (_Obj*)__p;
	
		//将其指针指向待释放的空间,并且链回到自由链表上
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;

    }
  }
```cpp

// 以《STL源码剖析》这本书的例子进行研究,先申请32字节空间,
然后申请64字节空间。
 //接着申请96字 节空间,最后申请72字节
 //(假设此时内存池耗尽、堆空间没有大于72字节的连续空间) 。

_free_list = _S_free_list + _S_freelist_index(__n);
        //_S_free_list[3]
      _Obj* __q = (_Obj*)__p;
	
		//将其指针指向待释放的空间,并且链回到自由链表上
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;

    }
  }

以《STL源码剖析》这本书的例子进行研究,先申请32字节空间,然后申请64字节空间,接着申请96字 节空间,最后申请72字节(假设此时内存池耗尽、堆空间没有大于72字节的连续空间) 。

二级空间配置器,直接将用完后的空间链回到相应的链表下面,使用头插法进行连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值