C++后台开发核心技术之STL篇 2017/5/14
来源:互联网 发布:往string数组添加元素 编辑:程序博客网 时间:2024/06/03 17:09
SGI STL配置器详解
SGI STL空间配置器是与众不同的,其名称是alloc而非allocator,而且不接受任何参数。如果你要在程序中采用SGI配置器,则不能采用标准写法:
vector<int,std::allocator<int> > iv;//in VC or CBvector<int,std::alloc> iv; //in GCC 必须这么写。
由于我们通常缺省使用配置器,所以这个问题并不会对我们造成困扰。SGI也定义了一套符合部分标准,名为allocator的配置器,但SGI从未使用过它,因为他只是对new以及delete进行了一层简单的封装,效率极低。SGI另有法宝供其自身使用,那就是特殊的空间配置器std::alloc,alloc具备两层结构,分别为一级配置器和二级配置器,第一层配置器直接使用maloc()和free()函数,二级配置器则视情况采用不同的策略,当配置区块超过128byte时,调用一级配置器,当配置区块小于128时,为了降低额外负担,采用复杂的memory pool整理方式。
SGI STL中可以选择是否采用二级适配器,有如下宏条件定义:
'''# ifdef __USE_MALLOCtypedef malloc_alloc alloc;//malloc_alloc为一级配置器# else...template <bool threads, int inst>class __default_alloc_template {...};//二级配置器
一级配置器的SGI STL源码如下,其原理比较简单,无需更多说明:
template <int __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)();#endifpublic: 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* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz) { 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); }};// malloc_alloc out-of-memory handling#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUGtemplate <int __inst>void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;#endiftemplate <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)(); __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); }}
第二级配置器__default_alloc_template剖析:
为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量调整至8的倍数,并且维护16个free-lists,各自管理大小为8到128这么16个小额区块。
union _Obj { union _Obj* _M_free_list_link; char _M_client_data[1]; /* The client sees this. */ };static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];
为了维护一个链表,每个节点需要额外的指针指向下一个节点,这会造成一定的浪费,STL早已准备好解决途径,那就是使用union来维护链表。当使用union第一字段时,union中的指针用来指向下一个节点,而当union使用第二字段时,union指针又可以指向实际用到的内存块首地址。(其实个人理解觉得没什么意义,因为一般申请的内存块都比较大。)
__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) /*__bytes不足8的倍数时,返回8的倍数*/ { 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. */ }; 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;
在分配内存时直接调用二级配置器,若大于128byte则使用一级配置器,二级配置器的基本接口函数如下:
public: /* __n must be > 0 */ static void* allocate(size_t __n) { void* __ret = 0; //如果__n>128则调用一级配置器 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所在链表首节点 _Obj* __RESTRICT __result = *__my_free_list; if (__result == 0) __ret = _S_refill(_S_round_up(__n));//如果链表首节点为空,表示该__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; __q -> _M_free_list_link = *__my_free_list;//将需要释放的小内存插入对应链表首节点。 *__my_free_list = __q; } } static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);//将p指针重新指向一块内存块。} ;
下面单独分析每个函数的源码:
调用二级配置器时,当对应链表为空时,需要_S_refill()函数向内存池(memory pool)重新申请,内存池的首地址为static char* _S_start_free,末尾地址为
static char* _S_end_free来维护。
/*if (__result == 0) __ret = _S_refill(_S_round_up(__n));//如果链表首节点为空,表示该__n大小内存区没有可用碎片。*/template <bool __threads, int __inst>void*__default_alloc_template<__threads, __inst>::_S_refill(size_t __n){ int __nobjs = 20;//默认申请20块区域 char* __chunk = _S_chunk_alloc(__n, __nobjs);//真正向内存池申请的函数,__nobjs传递参数为引用,__nobjs的返回值表示已申请到的个数。 _Obj* __STL_VOLATILE* __my_free_list; _Obj* __result; _Obj* __current_obj; _Obj* __next_obj; int __i; if (1 == __nobjs) return(__chunk);//当只申请到1块时,直接返回 __my_free_list = _S_free_list + _S_freelist_index(__n); /* Build free list in chunk */ __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()函数向内存池申请资源:
/* We allocate memory in large chunks in order to avoid fragmenting *//* the malloc heap too much. *//* We assume that size is properly aligned. *//* We hold the allocation lock. */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) { //如果剩余空间小于__size,则将剩余空间插入对应链表。 _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) { size_t __i; _Obj* __STL_VOLATILE* __my_free_list; _Obj* __p; /*当malloc申请内存失败时,我们将手伸向还没有被使用的链表区块,当然不会选择那些比__size还要小的区块,这样递归下去会导致效率变得很低。*/ 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));//此时内存池空间已经足够,递归调用_S_chunk_alloc来调整__nobjs的值以及内存块布局。 }}
为了使配置器拥有标准接口,STL选择将其做进一步的封装:
template<class _Tp, class _Alloc>class simple_alloc {public: static _Tp* allocate(size_t __n) { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); } static _Tp* allocate(void) { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); } static void deallocate(_Tp* __p, size_t __n) { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); } static void deallocate(_Tp* __p) { _Alloc::deallocate(__p, sizeof (_Tp)); }};
于是在vector类中可以看到是这样使用的:
typedef simple_alloc<value_type,Alloc> data_allocator;
配置器的大体实现就是这么多了,当然还有防止多线程的加锁方案没有加进去。
- C++后台开发核心技术之STL篇 2017/5/14
- C++后台开发STL之vector类2017/5/15
- C++后台开发核心技术实践学习日志2017/5/11
- App后台开发运维和架构实践学习总结(1)——App后台核心技术之用户验证方案
- 《后台开发核心技术与应用实践》(二)
- 《后台开发核心技术与应用实践》(三)
- 《后台开发核心技术与应用实践》(四)
- C++后台开发之编译与链接2017/5/12
- 【C++后台开发面试】STL相关
- linux c/c++ 后台开发基础之:c++日志模块
- linux c/c++ 后台开发之—连接池
- linux c/c++ 后台开发之—连接池
- c/c++ 后台开发常用组件之:c++日志模块
- C++STL之multimap
- C++STL之string
- C++STL之迭代器
- C STL 之算法
- C++STL之string
- android 支付sdk ---->libPaySdk
- OpenGL绘制三维贝塞尔曲线
- 回形遍历(算法)
- apache的基本信息及相关设置
- vb.net 教程 3-5 窗体编程 对话框3 ColorDialog & FontDialog
- C++后台开发核心技术之STL篇 2017/5/14
- BN层计算的为什么不是协方差矩阵的理解
- Codeforces Round #413 Div. 2 D. Field expansion
- 445端口入侵详解
- 初识.net界面程序(7)——泛型和LINQ练习
- 蓝桥杯:神奇算式
- HDU 2021 发工资咯:)
- 比特币病毒是什么?
- 445端口入侵详解