空间适配器

来源:互联网 发布:办公室网络综合布线 编辑:程序博客网 时间:2024/06/03 14:16
本篇博客主要剖析 STL 空间适配器源码,并自己造一个轮子。
参考资料:STL源码剖析,侯捷译
邮箱:blbagony@163.com
欢迎提出问题和建议
代码及源码

空间适配器

  • 为什么要有空间适配器:
    1. 处理内存碎片
      内存碎片
    2. 使 CPU 高效处理当前成程序
      每一次申请空空间,CPU 都会被打断一次

建议最好参照源代码,自己实现一遍,我自己实现的代码里也有注释,如果觉得源代码难读可以直接看我自己实现的,大佬们见笑了


这里写图片描述

一级空间配置器

这里写图片描述

代码分析
  • 申请空间
static void* allocate(size_t __n)  {    /*从系统申请 __n 个字节的内存,如果申请不到,调自己指定函数*/    void* __result = malloc(__n);    /*_S_oom_malloc 该函数就是反复不停的用用户自己指定的方法申请内存,可以在源码中自己看看*/    if (0 == __result) __result = _S_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)  {    /* 系统调用 realloc */    void* __result = realloc(__p, __new_sz);    /* 如果内存吃紧,就调用用户指定的方法申请空间 */    if (0 == __result) __result = _S_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);  }

通常情况不设定自己处理内存吃紧的时的处理函数,这就好比钱(内存)只够买一本书(进程),到底是买 C++ primer 还是买 effective C++ ,必须有一个你要需要放弃。最明知选择当然是不干扰其他进程什么都不做,这时如果申请内存失败会抛出异常(threw bad_alloc)


二级空间配置器

工作原理

  • 举个例子:小明每个月都需要向他妈要一千块生活费,但她妈经常打麻将觉得小明每次打电话太烦了,直接给了小明一学期的生活费,告诉小明这学期别来问她要钱了。

  • 在这个例子中小明(二级空间配置器),他要每天买早饭午饭和晚饭(分配小块儿内存),每个月有一千块生活费(内存池);小明的妈妈(二级空间配置配器)爱打麻将。

  • 由这个例子我们发现二级空间配置器需要一段实现分配的内存池这个内存池由两个 char* 类型的指针(start_free\end_free)维护。为什么 start_free/end_free 是 char* ?

  • 假如小明没有智能手机,每次消费只能付现金,所以小明手里一定有很多面值不同的钞票(不同大小的内存块儿),这些面值不同的钞票放在小明的钱包里(free_list)。

  • 这个例子中钱包就是用来存放不同面值钞票的 free_list 钞票表示不同大小的内存块儿,为什么钞票最小是 8 个字节

    1. 为什么是 char* 而不是 void* 或者 int* 偏偏是 char* 呢?
      原因是每次申请空间以字节为单位,将 start 和 end 定义成 char* 方便移动。

    2. 为什最小单位是 8 个字节?
      原因是free_lsit 上挂载的内存块儿需要存一个指向下一块儿内存的指针,指针大小在 32 平台下占四个字节,在 64 操作系统下占 8 个字节,所以这样做是为了代码的可移植性

以上,我们分析清楚了二级配置器的零件,现在我们可以自己实现了

代码分析

这里写图片描述
- 零件

  /*最小内存块儿,单位字节*/  enum {_ALIGN = 8};  /*最大内存块儿,单位字节*/  enum {_MAX_BYTES = 128};  /*钱包空间,free_lsit 的大小*/  enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
  • 挂载的内存块儿
/*联合体,里面有一个指针指向下一块儿内存*/union _Obj {        union _Obj* _M_free_list_link;        char _M_client_data[1];    /* The client sees this.        */  };

维护内存池的组件

  /*内存池的首地址*/  static char* _S_start_free;  /*内存池的尾地址*/  static char* _S_end_free;  /*已向以及配置器申请的内存大小,单位字节*/  static size_t _S_heap_size;

对齐内存块儿

  /*现在用户要申请大小为 23 字节的内存,这时就需要对齐到 24 个字节*/  static size_t  _S_round_up(size_t __bytes)    /* _ALIGN = 8 ,这行代码是为了把当前 size 增大到下一个 8 的倍数的数量级*/     { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

计算挂载大小为 size 的内存块的下标

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

问系统先申请一块大内存

template <bool __threads, int __inst>char*__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,                                                             int& __nobjs){    char* __result;    /*要多少内存,默认一次性多给二十块儿,__nobjs = 20*/    size_t __total_bytes = __size * __nobjs;    /*当前内存池剩余的内存*/    size_t __bytes_left = _S_end_free - _S_start_free;        /*****************************************        *       申请大块儿内存,分三种情况        *   1. 内存池足够,从内存池里取        *   2. 内存池不够,但能取小于 nobjs 个内存块        *   3. 内存池连一个都取不出来        *****************************************/    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.        /*把内存池剩余的内存挂载到 free_list 对应的位置,注意如果内存剩余 24 则挂载到 24 对应的位置上,为什么不分成 3 个挂载到 8 对应的位置处*/        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);        if (0 == _S_start_free) {        /*系统内存吃紧,只能在 free_list 挂载更大的内存块儿上摘*/            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.            /*从 _S_free_list 上找一个比当前所申请内存块儿更大的内存*/            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) {                /*从 _S_free_list 找到了,放到内存池里,重新切分,下一次就可以从第二种情况返回*/                    *__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 清零,如果从一级申请成功,再次计算剩余内存池剩余内存会将已经分配的内存算上*/        _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));    }}


往自由链表上挂连续 20 个大小相同的内存块儿

/* Returns an object of size __n, and optionally adds to size __n free list.*//* We assume that __n is properly aligned.                                *//* We hold the allocation lock.                                         */template <bool __threads, int __inst>void*__default_alloc_template<__threads, __inst>::_S_refill(size_t __n){    int __nobjs = 20;    char* __chunk = _S_chunk_alloc(__n, __nobjs);    _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);    /* Build free list in chunk */    /*挂内存块儿,使用尾插,为什么使用尾插?使用尾插是因为申请到的内存是连续的,可增加 CPU 第一次从内存池中取内存时的命中率*/      __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);}

向内存池申请大为 size 的内存

  /* __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 {       /*从 _S_free_list 找到存放内存大小为 __n 的内存块地址,n 需要对齐到 8 的整数倍*/      _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/*如果内存池里没有,就调用 _S_refill 返回一个指向大小为 __n 对齐到 8 的整数倍大小内存块的指针*/      _Obj* __RESTRICT __result = *__my_free_list;      if (__result == 0)        __ret = _S_refill(_S_round_up(__n));      else { /*如果有,用头删把这块儿内存从 _free_list 取出来*/        *__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)  {  /*如果是大块儿内存,调用一级配置器的回收函数(直接去关联 free)*/    if (__n > (size_t) _MAX_BYTES)      malloc_alloc::deallocate(__p, __n);    else {    /*重新挂载到 _free_list 上,所用方式是尾插*/      _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    }  }

我们都是站在巨人的肩膀上,加油!