SGI STL allocator

简单实现是一个allocator

C++模板及实现vector_~怎么回事啊~的博客-CSDN博客

        SGI STL包含了一级空间配置器和二级空间配置器,其中一级空间配置器allocator采用malloc和free来管理内存,和C++标准库中提供的allocator是一样的,但其二级空间配置器allocator采用了基于freelist自由链表原理的内存池机制实现内存管理。

        空间配置器:是操作系统开辟的一大段内存空间。STL需要扩容申请内存时,就从空间配置器中申请,不需要再经过操作系统。并且,它还能回收释放的空间,供下一次使用

        优点

  • 提高效率 : STL容器申请空间不需要频繁向操作系统申请,而是需要向空间适配器申请,只是当空间适配器空间不够时,才会向操作系统申请。
  • 避免内存碎片
  • 更好的管理内存

        空间配置器原理
        空间配置器有两级结构,一级空间配置器是用来处理大块内存,二级空间配置器处理小块内存。SGI-STL规定以128字节作为小块内存和大块内存的分界线。

为什么这样区分成两级?

        因为STL容器,一般申请的都会是小块的内存。

        二级空间配置器,主要是管理容器申请空间和释放的空间。

        如果用户申请的空间直接大于的128字节直接找的是一级空间配置器申请空间。
 

源码资源:https://download.csdn.net/download/LIJIWEI0611/86742292

stl_vector.h

stl_construct.h

 

 

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc> 
其中:

# ifndef __STL_DEFAULT_ALLOCATOR
#   ifdef __STL_USE_STD_ALLOCATORS     使用标准allocator
#     define __STL_DEFAULT_ALLOCATOR(T) allocator< T >   
#   else
#     define __STL_DEFAULT_ALLOCATOR(T) alloc
#   endif
# endif

一级空间配置器

 一级空间配置器原理很简单,直接是对malloc和free进行了封装,并且增加了C++中的set_new_handle思想,即申请空间失败抛异常机制。

        主要的作用是:向操作系统申请内存,申请失败会抛异常。

        为什么不直接用C++的new和delete,因为这里并不需要调用构造函数和析构函数。

        用户可以自定义申请空间失败的措施,如果没有设置,就会抛异常。

// This implements allocators as specified in the C++ standard.  
//
// Note that standard-conforming allocators use many language features
// that are not yet widely implemented.  In particular, they rely on
// member templates, partial specialization, partial ordering of function
// templates, the typename keyword, and the use of the template keyword
// to refer to a template member of a dependent type.

#ifdef __STL_USE_STD_ALLOCATORS

template <class _Tp>
class allocator {
  typedef alloc _Alloc;          // The underlying allocator.
public:
  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> other;
  };

  allocator() __STL_NOTHROW {}
  allocator(const allocator&) __STL_NOTHROW {}
  template <class _Tp1> allocator(const allocator<_Tp1>&) __STL_NOTHROW {}
  ~allocator() __STL_NOTHROW {}

  pointer address(reference __x) const { return &__x; }
  const_pointer address(const_reference __x) const { return &__x; }

  // __n is permitted to be 0.  The C++ standard says nothing about what
  // the return value is when __n == 0.
  //使用_Alloc::allocate
  _Tp* allocate(size_type __n, const void* = 0) {
    return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp))) 
                    : 0;
  }

  // __p is not permitted to be a null pointer.
  //使用 _Alloc::deallocate
  void deallocate(pointer __p, size_type __n)
    { _Alloc::deallocate(__p, __n * sizeof(_Tp)); }

  size_type max_size() const __STL_NOTHROW 
    { return size_t(-1) / sizeof(_Tp); }

  //使用定位new
  void construct(pointer __p, const _Tp& __val) { new(__p) _Tp(__val); }
  //调用析构函数
  void destroy(pointer __p) { __p->~_Tp(); }
};

alloc 


typedef malloc_alloc alloc;
typedef __malloc_alloc_template<0> malloc_alloc;

template <int inst>
class __malloc_alloc_template
{
private:
	static void *oom_malloc(size_t);
public:
	// 对malloc的封装
	static void * allocate(size_t n) {
		// 申请空间成功,直接返回,失败交由oom_malloc处理
		void *result = malloc(n);
		if (0 == result)
			result = oom_malloc(n);
		return result;
	}
	// 对free的封装
	static void deallocate(void *p, size_t /* n */)
	{
		free(p);
	}
	// 模拟set_new_handle
	// 该函数的参数为函数指针,返回值类型也为函数指针
	// void (* set_malloc_handler( void (*f)() ) )()
	static void(*set_malloc_handler(void(*f)()))()
	{
		void(*old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = f;
		return(old);
	}
};
 
// malloc申请空间失败时代用该函数
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {
	void(*my_malloc_handler)();
	void *result;
	for (;;)
	{
		// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler)
		{
			__THROW_BAD_ALLOC;
		}
 
		// 如果设置,执行用户提供的空间不足应对措施
		(*my_malloc_handler)();
 
		// 继续申请空间,可能就会申请成功
		result = malloc(n);
		if (result)
			return(result);
	}
}
typedef __malloc_alloc_template<0> malloc_alloc;

二级空间配置器

        二级空间配置器专门负责处理小于128字节的小块内存。

        SGI-STL采用了内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度和高效管理。

        这样就避免了频繁向系统申请小块内存找出的效率低,内存碎片的问题。

但是这样会有一个问题:如何归还?

        用户申请空间很简单,直接从内存池中申请。但是,当用户释放该空间时,并不知道这块空间应该放在内存池的什么位置。归还成了一个问题。

        在STL配置器中,使用哈希桶的技术来解决这一问题。

哈希桶技术


        说明:用户申请的空间基本上都会是4的整数倍,其它空间大小几乎很少用到。所以哈希桶并不需要对1~128字节的空间进行管理,也就是并不需要128个桶。SGI-STL将用户申请的内存块向上对齐到了8字节的整数倍。

为什么不用链表来管理归还的空间?

        因为用户申请空间,查找合适的内存块时,效率低。

为什么向上对齐到了8字节的整数倍?

        因为在哈希桶下,是以链表的形式将所有内存块连接起来的。该内存块至少需要保存下一个内存块的地址,在64位系统下,为8字节。

        但是这样造成了内存碎片问题。

        用户归还的空间,会被连接到哈希桶对应空间大小的位置下。
 

大致流程:

        容器进行扩容,如果申请的空间是大于128字节,直接向一级空间配置器申请。如果小于128字节,先查找哈希桶对应大小位置是否为空,不为空,直接从该位置申请空间,如果该位置为空,向内存池申请。当内存池空间不够了会直接向OS申请一大块空间。

注意点:

        用户向内存池申请一块大小为n的空间时,为了效率,内存池会切割出多块大小为n的空间,给用户一个,其它剩余的会连接到哈希桶对应下标处。避免下一次申请。

        可能用户申请的空间会不是8的整数倍,内存池切割的内存块大小会向上对齐到8的整数倍。比如:申请5字节空间,内存池会切割8字节的空间。

        一个进程中有一个空间配置器,进程中所有容器需要的空间都到对应空间配置器申请。进程终止,对应空间配置器空间释放。

内存碎片问题


      外碎片
        由于频繁申请小块内存,导致整块内存中,被申请的内存块不连续,碎片化了,如果下一次需要申请一大块内存,内存空间够,但是由于不连续,导致申请不出来。

         内核针对大量申请在堆上小块内存导致碎片化的问题,是用来slab分配器来解决。结构类似二级空间配置器的哈希结构。

        内核已经有了slab分配器来解决小块内存的申请,为什么STL中还有设计一个?

        因为首先内核是针对所有程序的,并不是只指针对STL。其次STL配置器主要是为了解决效率问题,不需要频繁项OS申请空间,只是顺便解决了内存碎片问题。

        内碎片
        内碎片问题:给的内存数比实际要的内存数多,导致空间浪费。

        二级配置器切割内存块向上对齐8的整数倍,就造成了内碎片问题
源码:

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;

template <bool threads, int inst>
class __default_alloc_template {

private:
  // Really we should use static const int x = N
  // instead of enum { x = N }, but few compilers accept the former.
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))
    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
# endif
  static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

__PRIVATE:
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
private:
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    static _Obj* __STL_VOLATILE _S_free_list[]; 
        // Specifying a size results in duplicate def for 4.1
# else
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
# endif
  static  size_t _S_freelist_index(size_t __bytes) {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }

  // Returns an object of size __n, and optionally adds to size __n free list.
  static void* _S_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.
  static char* _S_chunk_alloc(size_t __size, int& __nobjs);

  // Chunk allocation state.
  static char* _S_start_free;
  static char* _S_end_free;
  static size_t _S_heap_size;

# ifdef __STL_THREADS
    static _STL_mutex_lock _S_node_allocator_lock;
# endif

    // It would be nice to use _STL_auto_lock here.  But we
    // don't need the NULL check.  And we do need a test whether
    // threads have actually been started.
    class _Lock;
    friend class _Lock;
    class _Lock {
        public:
            _Lock() { __NODE_ALLOCATOR_LOCK; }
            ~_Lock() { __NODE_ALLOCATOR_UNLOCK; }
    };

public:

  /* __n must be > 0      */
  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      // 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
      _Obj* __RESTRICT __result = *__my_free_list;
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

  /* __p may not be 0 */
  static void deallocate(void* __p, size_t __n)
  {
    if (__n > (size_t) _MAX_BYTES)
      malloc_alloc::deallocate(__p, __n);
    else {
      _Obj* __STL_VOLATILE*  __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      _Obj* __q = (_Obj*)__p;

      // acquire lock
#       ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#       endif /* _NOTHREADS */
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;
      // lock is released here
    }
  }

  static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);

} ;

重要类型和变量定义

先是三个枚举量,表示粒度信息

// 内存池的粒度信息
enum {
_ALIGN = 8}; // 8的倍数递增
enum {
_MAX_BYTES = 128}; // 分配的最大字节数128
enum {
_NFREELISTS = 16}; // 自由链表个数(数组长度)

这个是每个内存chunk块的信息,实际上可当作链表,这个_M_free_list_link相当于next域。

// 每一个内存chunk块的头信息
union _Obj {

union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};

这个指针数组就是维护16个自由链表,先记住数组名(_S_free_list)

// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为0
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];

这三个静态变量是维护内存池(向堆区申请的)。前两个指针变量,表示内存池的开始位置和结束位置,而heap_size表示向堆区申请的字节总数。先将他们全初始化为0。

// Chunk allocation state. 记录内存chunk块的分配情况
static char* _S_start_free;
static char* _S_end_free;
static size_t _S_heap_size;
template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;
template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;
template <bool __threads, int __inst>
size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;

两个重要辅助接口函数

  1. _S_round_up

意义是将客户端想要申请的字节数上调到8的倍数。比如申请9各字节,就上调到16个字节;申请20字节,就上调到24个字节。原理是位操作。

/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t _S_round_up(size_t __bytes) {

return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1));
}
  1. _S_freelist_index

意思是找到要去申请的具体的自由链表。下标从0开始,所以用的 '/'运算符。

/*返回 __bytes 大小的chunk块位于 free-list 中的编号*/
static size_t _S_freelist_index(size_t __bytes) {

return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
}

内存池管理函数

// 分配内存的入口函数
static void* allocate(size_t __n);
// 负责把分配好的chunk块进行连接,添加到自由链表当中
static void* _S_refill(size_t __n);
// 分配相应内存字节大小的chunk块,并且给下面三个成员变量初始化
static char* _S_chunk_alloc(size_t __size, int& __nobjs);
// 把chunk块归还到内存池
static void deallocate(void* __p, size_t __n);
// 内存池扩容函数
template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void* __p,
size_t __old_sz,
size_t __new_sz);

 allocate

  /* __n must be > 0      */
  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);//返回 __n大小的chunk块位于 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
      _Obj* __RESTRICT __result = *__my_free_list;
      if (__result == 0)//如果从来没有申请过
        __ret = _S_refill(_S_round_up(__n));//将 __n 上调至最邻近的 8 的倍数
      else {
        让 __my_free_list 指向 当前空闲的块的 下一个 空闲的块 的地址()
        //因为Obj* __RESTRICT __result = *__my_free_list;当前空闲的首地址被分配出去
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

 _S_refill

 自由链表是一个指针数组,有点类似hash桶,它的数组大小为16,每个数组元素代表所挂的区块大小,比如free _ list[0]代表下面挂的是8bytes的区块,free _ list[1]代表下面挂的是16bytes的区块…….依次类推,直到free _ list[15]代表下面挂的是128bytes的区块
内存池,以start _ free和 end _ free记录其大小,用于保存未被挂在自由链表的区块,它和自由链表构成了伙伴系统。自由链表对应的位置没有所需的内存块该怎么办,也就是Refill函数的实现

//freelist没有可用区块,将要填充,此时新的空间取自内存池
static void* Refill(size_t n)
{
    size_t nobjs = 20;
    char* chunk = (char*)ChunkAlloc(n, nobjs); //默认获得20的新节点,但是也可能小于20,可能会改变nobjs
    if (nobjs == 1) // 如果只获得一个数据块,那么这个数据块就直接分给调用者,空闲链表中不会增加新节点
        return chunk;
    //有多块,返回一块给调用者,其他挂在自由链表中
    Obj* ret = (Obj*)chunk;
    Obj* cur = (Obj*)(chunk + n);
    Obj* next = cur;
    Obj* volatile *myFreeList = FreeList + FreeListIndex(n);
    *myFreeList = cur;
    for (size_t i = 1; i < nobjs; ++i)
    {       
        next = (Obj*)((char*)cur + n);
        cur->freeListLink = next;
        cur = next;
    }
    cur->freeListLink = NULL;
    return ret;
 
 
}

_S_chunk_alloc 

// 从内存池中取空间
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
                                                            int& __nobjs)
{
    char* __result;
    size_t __total_bytes = __size * __nobjs;  // 需要申请空间的大小
    size_t __bytes_left = _S_end_free - _S_start_free;  // 计算内存池剩余空间
    if (__bytes_left >= __total_bytes) {  // 内存池剩余空间完全满足申请
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else if (__bytes_left >= __size) {  // 内存池剩余空间不能满足申请,提供一个以上的区块
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else {                             // 内存池剩余空间连一个区块的大小都无法提供                      
        size_t __bytes_to_get =
          2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        // 内存池的剩余空间分给合适的空闲链表
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);
            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        _S_start_free = (char*)malloc(__bytes_to_get);  // 配置 heap 空间,用来补充内存池
        if (0 == _S_start_free) {  // heap 空间不足,malloc() 失败
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
            _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;
                if (0 != __p) {
                    *__my_free_list = __p -> _M_free_list_link;
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
            _S_end_free = 0;   // In case of exception.
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);  // 调用第一级配置器
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        return(_S_chunk_alloc(__size, __nobjs));  // 递归调用自己
    }
}

        上述的 chunk_alloc()函数以end_free- start free来判断内存池的水量。如果水量充足,就直接调出20个区块返回给 free list如果水量不足以提供20个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去。这时候其 pass by reference的 nobjs参数将被修改为实际能够供应的区块数。如果内存池连一个区块空间都无法供应,对客端显然无法交待,此时便需利用malloc()从heap中配置内存,为内存池注入活水源头以应付需求。新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量.以上便是整个第二级空间配置器的设计。

 

假设第一次:分配5字节

 

第一次_S_chunk_alloc 后:

然后又递归走到 _S_chunk_alloc,此时

完成20个8字节chunk区域的分配,并且还预留了20个8字节的空间没有使用;

假设1 :之后如果需要分配16字节的空间:

_S_chunk_alloc 之前的操作相同,当走到_S_chunk_alloc时,备用内存池中还有160字节未使用,继续使用:

假设2 :之后如果需要分配128字节的空间:

_S_chunk_alloc 之前的操作相同,当走到_S_chunk_alloc时,备用内存池中还有160字节未使用,继续使用:图如下

剩余160字节只能申请1个128字节的obj,然后剩余32字节。如果此时需要40字节的大小空间,此时剩余的字节已经不够了:此时

size_t __total_bytes =20 * 40 = 800;

size_t __bytes_left = 32;剩余可用字节

_S_heap_size = 320(已经开辟):

此时走到 else

else {                             // 内存池剩余空间连一个区块的大小都无法提供  

        //__bytes_to_get = 2 * 800 + 320/16 = 1600+ 24 = 1624 字节                    
        size_t __bytes_to_get =
          2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        // 内存池的剩余空间分给合适的空闲链表,这里将剩余的32字节分配到32字节的窗口块
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);
            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        _S_start_free = (char*)malloc(__bytes_to_get);  // 配置 heap 空间,用来补充内存池
        if (0 == _S_start_free) {  // heap 空间不足,malloc() 失败
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
            _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            //如果内存分配失败,那么将从下一个+8 的chunk(这里即48-128)中找空闲的块拿来用
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;
                if (0 != __p) {
                    *__my_free_list = __p -> _M_free_list_link;
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }

            //后面的chunk都不能找到到空闲的块了
            _S_end_free = 0;   // In case of exception.
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);  // 调用第一级配置器
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        return(_S_chunk_alloc(__size, __nobjs));  // 递归调用自己

当后续的list中 chunk都没有空闲空间,则走到

 _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);

  
static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);//有可能分配成功,如果此时有线程释放内存
    if (0 == __result) __result = _S_oom_malloc(__n);//如果不能正常分配
    return __result;
  }


template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
    void (* __my_malloc_handler)();
    void* __result;

    //死循环:如果设置了分配失败的处理函数则死循环,否则抛异常OOM
    for (;;) {
        __my_malloc_handler = __malloc_alloc_oom_handler;//预先设置的失败的回调函数
        if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }//如何没有设置,则抛异常OOM
        (*__my_malloc_handler)();//执行用户设置的分配失败的处理函数
        __result = malloc(__n);//重新malloc
        if (__result) return(__result);
    }
}

该内存池的优点

  1. 对于每一个字节数的chunk块分配,都是给出一部分进行使用,另一部分作为备用,这个备用可以给当前字节数使用,也可以给其它字节数使用。

  2. 对于备用内存池划分完chunk块以后,如果还有剩余的很小的内存块,再次分配的时候,会把这些小的内存块再次分配出去,备用内存池使用的干干净净!

  3. 当指定字节数内存分配失败以后,有一个异常处理的过程,bytes - 128字节所有的chunk块进行查看,如果哪个字节数有空闲的chunk块,直接借一个出去

  4. 如果上面的操作失败,还会调用 _oom_malloc这个提前设置好的malloc内存分配失败的回调函数,
    如果没设置就会抛出异常(throw_bad_alloc)
    如果设置了,就会启动一个无限循环 for(; ; ),一直调用(*oom_malloc_handler)();函数,完了后继续调用malloc()。。。

reallocate

template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void* __p,
                                                    size_t __old_sz,
                                                    size_t __new_sz)
{
    void* __result;
    size_t __copy_sz;
    
    //大于128字节
    if (__old_sz > (size_t) _MAX_BYTES && __new_sz > (size_t) _MAX_BYTES) {
        return(realloc(__p, __new_sz));
    }
    // 8的倍数时相等
    if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);

    // 调用allocate 重新分配
    __result = allocate(__new_sz);
    // 有可能时扩容或者缩容
    __copy_sz = __new_sz > __old_sz? __old_sz : __new_sz;
    memcpy(__result, __p, __copy_sz);
    //归还老的内存
    deallocate(__p, __old_sz);
    return(__result);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值