STL源码剖析--空间配置器alloc

空间的配置与释放std::alloc

SGI对对象构造前的空间配置与对象析构的空间释放设计哲学为:
* 向system heap要求空间。
* 考虑多线程状态。
* 考虑内存不足时的应变措施。
* 考虑过多“小型区块”可能造成的内存碎片问题。

在C++中内存配置的基本操作是::operator new(),内存释放基本操作是::operator delete()。而这两个全局函数相当于C中的malloc()free()函数。

对于为什么会有“小型区块”可能造成的内存碎片问题,我们先了解动态存储器的分配

动态存储器分配器维护着一个进程的虚拟存储器区域——堆。
这里写图片描述
对于每个进程,内核维护着一个变量brk,指向堆的顶部。
C标准库中提供了malloc程序包的显示分配器,通过调用malloc函数来从堆中分配块。

void *malloc(size_t size);
//该函数返回一个指针,指向大小至少为size字节的存储器块。

malloc操作在堆上都做了哪些事情呢?分配的大小为size吗?

任何实际的分配器都需要一些数据结构,允许它来区别块边界,以及区别已分配和空间块。大多数分配器将这些信息嵌入在块本身。所以我们就可以知道,当我们调用malloc(size_t size)时,在堆上所开辟的空间要大于size字节。
这里写图片描述
对于小内存分配问题,块中的头部和填充部分就会占到很大比例,那么就造成了内存的利用率不高。

对于小型区块造成的内存破碎问题,SGI设计了双层级配置器:
1.第一级配置器直接使用malloc()free()
2.第二级配置器视情况采用不同策略:

a. 当配置区块超过128 bytes时,使用第一级配置器。
b. 当配置区块小于128 bytes时,SGI便采用内存池(memory poor)整理方式,而不再求助于第一配置器。

一、二级配置器的关系,接口包装,及运用方式:

第一级配置器:__malloc_alloc_template

template <int __inst>   //使用非型别参数 __inst没有排上用场
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
  static void (* __malloc_alloc_oom_handler)();
#endif

public:
  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);   //第一级配置器直接使用malloc()
    //如果malloc因为内存不足(out-of-memory)分配失败,则使用_S_oom_malloc()
    if (0 == __result) __result = _S_oom_malloc(__n);
    return __result;
  }

  static void deallocate(void* __p, size_t /* __n */)
  {
    free(__p);  //第一级配置器直接使用free()
  }

  static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
  {
    void* __result = realloc(__p, __new_sz);    //此处与malloc类似
    if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
    return __result;
  }

//此处仿真C++的set_new_handler()。我们可以通过它指定自己的out-of-memory handler
  static void (* __set_malloc_handler(void (*__f)()))()
  {
    void (* __old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = __f;
    return(__old);
  }

};

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0; //初值为0,有待set_malloc_handler设定

#endif

template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_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)(); //调用用户自己定义的异常处理例程,set_malloc_handler
        __result = malloc(__n); //再次配置内存
        if (__result) return(__result);
    }
}

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, 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 = realloc(__p, __n);
        if (__result) return(__result);
    }
}
//将__inst指定为0
typedef __malloc_alloc_template<0> malloc_alloc;
#ifndef __THROW_BAD_ALLOC
#  if defined(__STL_NO_BAD_ALLOC) || !defined(__STL_USE_EXCEPTIONS)
#    include <stdio.h>
#    include <stdlib.h>
#    define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
#  else /* Standard conforming out-of-memory handling */
#    include <new>
#    define __THROW_BAD_ALLOC throw std::bad_alloc()
#  endif
#endif

第二级配置器:__default_alloc_template

第二级配置器解决了太多小额区块造成的内存的碎片以及malloc分配内存的额外负担问题。

SGI的做法是,如果区块够大,超过128 bytes时,就移交第一级配置器,当区块小于128 bytes时,则以内存池管理(memory poor),又被称为次配置器:每次配置一大块内存,并维护对应之自由链表(free-lists)。下次如果由相同的内存需求,就直接从free-lists中拔出。如果客端释放小额区块,就由配置器回收到free-lists

为了方便管理,SGI第二级配置器会自动将任何小额区块的内存需求量上调到8的倍数(如客端需要30bytes,就自动分配32bytes),并维护16个free-lists,各自管理8,16,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块。

enum {_ALIGN = 8}; //小型区块的上调边界
enum {_MAX_BYTES = 128}; //小型区块的上界
enum {_NFREELISTS = 16}; //free-lists个数

将小额区块的内存需求量上调至8的倍数:

static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

free-lists的节点结构:

union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
//16个free-lists
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; // __STL_VOLATILE 表示从原始内存读取变量数据,不进行优化。

//根据区块大小,决定使用第几号free-lists;free-lists从0号开始计算
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);
  // 配置一大块空间,可容纳nobjs个大小为“size”的区块
  // 如果配置nobjs个区块不方便,nobjs将会降低
  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; 

static data member的定义与初值设定

static void* allocate(size_t __n){/***/}
static void deallocate(void* __p, size_t __n){/***/}
static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);
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;

template <bool __threads, int __inst>
typename __default_alloc_template<__threads, __inst>::_Obj* __STL_VOLATILE
__default_alloc_template<__threads, __inst> ::_S_free_list[
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    _NFREELISTS
# else
    __default_alloc_template<__threads, __inst>::_NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

猜你喜欢

转载自blog.csdn.net/u013635579/article/details/79942657
今日推荐