STL源码剖析-Allocator
一、第一级配置器
顾名思义,Allocator是用来做内存分配的。
第一级配置器指的是当需要128bytes以上的内存时直接使用malloc和realloc进行内存分配,代码如下:
/*** @ 第一级配置器** @ 2023/04/07*/template<int __inst>class __malloc_alloc_template {private:static void* _S_oom_malloc(size_t);static void* _S_oom_realloc(void*, size_t);static void(*__malloc_alloc_oom_handler)();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) {free(__p);}static void* reallocate(void* __p, 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);}};// 初始化静态成员template <int __inst>void(*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = nullptr;template <int __inst>void*__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n) {void* __result;for (;;){if (__malloc_alloc_oom_handler != nullptr)__malloc_alloc_oom_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* __result;for (;;){if (__malloc_alloc_oom_handler != nullptr)__malloc_alloc_oom_handler();__result = realloc(__p, __n);if (__result)return __result;}}typedef __malloc_alloc_template<0> malloc_alloc;
1、这里模板的定义为template<int __inst>是什么意思呢?既然指定了int类型为什么还要用模板呢?
__malloc_alloc_template中给出的是静态函数接口,所以callback/handler也得是静态的形式,但是我想让不同的Allocator拥有不同的callback/handler,那就只有定义多个Allocator类了(Allocator1,Allocator2,Allocator3......)。有没有办法可以解决这个问题?
template<int __inst>就可以解决上面的问题。我们用不同的参数显式实例化__first_stage_alloc,拿到的静态成员地址是不同的,所以我们就可以给他们注册不同的callback/handler函数。
2、__set_malloc_handler这是返回值为函数指针的函数~可以学习一下。
3、malloc(0) 会出现什么情况?是可以返回一个有效指针的,内存的大小是由机器决定的,具体可以去查阅更多资料。
二、第二级配置器
原先我以为这里的内存池是类似ringbuffer一样的东西,所以看这段代码时觉得很迷。其实维护的是这样一个东西:
bufferpool分为两个部分,一个部分是用_S_free_list维护的链表,另一部分是还未分配到链表的buffer。
buffer(小于等于128bytes)分配的步骤如下:
a.先从链表中获取buffer chunk
b.如果链表中没有了,从pool中获取chunk加入到链表中
c.如果pool中没有了,那么重新分配buffer到链表中,再返回给调用者
buffer(小于等于128bytes)销毁的步骤:找到对应链表,插入到链表头部
template <int __inst>class __default_alloc_template {private:enum { _ALIGN = 8 };enum { _MAX_BYTES = 128 };enum { _NFREELISTS = _MAX_BYTES/_ALIGN };// 8 bytes alignstatic size_t _S_round_up(size_t __bytes) {return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));}union _Obj {union _Obj* _M_free_list_link;char _M_client_data[1];};// free list数组static _Obj* volatile _S_free_list[_NFREELISTS];// 根据区块大小决定使用第几号区块(0-15)static size_t _S_freelist_index(size_t __bytes) {return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);}// static void* _S_refill(size_t __n);// static char* _S_chunk_alloc(size_t __size, int& __nobjs);// 内存池起始位置static char* _S_start_free; // 内存池结束位置static char* _S_end_free; static size_t _S_heap_size;public:static void* allocate(size_t __n) {void* __ret = 0;if (__n > (size_t)_MAX_BYTES) {__ret = malloc_alloc::allocate(__n);}else{// 找到合适大小的链表_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__n);_Obj* __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;}static void deallocate(void* __p, size_t __n) {if (__n > (size_t)_MAX_BYTES)malloc_alloc::deallocate(__p, __n);else{_Obj* 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);};template <int __inst>char* __default_alloc_template<__inst>::_S_start_free = 0;template <int __inst>char* __default_alloc_template<__inst>::_S_end_free = 0;template <int __inst>size_t __default_alloc_template<__inst>::_S_heap_size = 0;// 使用模板类对象,需要在类名前面使用typenametemplate <int __inst>typename __default_alloc_template<__inst>::_Obj* volatile__default_alloc_template<__inst>::_S_free_list[__default_alloc_template<__inst>::_NFREELISTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };template<int __inst>char* __default_alloc_template<__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) { /* 如果剩余空间总空间 > n x size,将剩余buffer加入到链表中,最多加20块*/__result = _S_start_free;_S_start_free += __total_bytes;return(__result);}else if (__bytes_left >= __size) { /* n x size > 剩余空间总空间 > size, 将剩余buffer加入到链表中 */__nobjs = (int)(__bytes_left / __size);__total_bytes = __size * __nobjs;__result = _S_start_free;_S_start_free += __total_bytes;return(__result);}else { // 剩余空间总空间 < size// 分配2倍的total bytes buffer给buffer poolsize_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4);// 如果pool中还有剩余buffer,将剩余buffer加入到对应的链表中if (__bytes_left > 0) {_Obj* 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;}// 分配buffer_S_start_free = (char*)malloc(__bytes_to_get); if (0 == _S_start_free) {// 如果分配失败,则从其他链表中先获取一块buffersize_t __i;_Obj* volatile* __my_free_list;_Obj* __p;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));}}// 如果其他链表中也没有则调用第一级分配器中的allocate,这里有异常处理_S_end_free = 0;_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);}_S_heap_size += __bytes_to_get;_S_end_free = _S_start_free + __bytes_to_get;// 将pool中的buffer加入到链表中return(_S_chunk_alloc(__size, __nobjs));}}template<int __inst>void* __default_alloc_template<__inst>::_S_refill(size_t __n){// 每个链表最多维护20块bufferint __nobjs = 20;char* __chunk = _S_chunk_alloc(__n, __nobjs);_Obj* 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;}template <int __inst>void* __default_alloc_template<__inst>::reallocate(void* __p, size_t __old_sz, size_t __new_sz){void* __result;size_t __copy_sz;if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {return(realloc(__p, __new_sz));}if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);__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);}typedef __default_alloc_template<0> alloc;
1、这里的8 bytes align很是巧妙:将原先的数字加7进位,然后用与运算减去余数。
2、使用union维护链表来节省内存
书里和网上查到的资料对此特性的描述:"用联合体union来维护链表,从其第一个字段观之可以视为一个指针,指向下一个区块;从其第二个字段观之是存有本块内存首地址"。一开始我不能理解这句话,后来我发现联合体中成员的地址等于联合体的地址:
union _Obj {union _Obj* _M_free_list_link;char _M_client_data[1];};int v = 10;_Obj *o = (_Obj*)&v;printf("%x\n", o->_M_client_data); // 0xb8fb2cprintf("%x\n", &(o->_M_free_list_link)); // 0xb8fb2c
数组本身就是一个地址,打印数组就是打印的内存块的首地址,而这个数组具体有几个元素并不重要。
指针本身也是一个变量,有自己地址,成员指针的地址等同于union的地址,给成员指针赋值等同于给union存下一块buffer的地址。
3、用typename来表示这是一个类型
template <int __inst>typename __default_alloc_template<__inst>::_Obj* volatile__default_alloc_template<__inst>::_S_free_list[__default_alloc_template<__inst>::_NFREELISTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
模板显式实例化:
template typename TinySTL::__default_alloc_template<0>;
4、看到这里我还有几个问题?
deallocate并没有真正释放小于128bytes的buffer,随着buffer分配的越来越多,这里的处理是不是欠妥?
分配出来的buffer是一整块buffer中的一部分,写的时候不对size进行限制,指针越界怎么处理呢?
后续看到了再来解答.
三、标准空间配置器
上面是SGI的高效率配置器,除了这个,SGI还有一个标准的配置器,以下是精简的代码:
namespace TinySTL {typedef int ptrdiff_t;template<typename T>inline T* allocate(ptrdiff_t size, T*) {T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));if (tmp == 0){printf("out of memory\n");exit(1);}return tmp;}template<typename T>inline void deallocate(T* buffer) {::operator delete(buffer);}template<typename T>class allocator {public:typedef T value_type;typedef T* pointer;typedef const T* const_pointer;typedef T& reference;typedef const T& const_reference;typedef size_t size_type;typedef ptrdiff_t difference_type;pointer allocate(size_type n) {return TinySTL::allocate((difference_type)n, (pointer)0);}void deallocate(pointer p) {TinySTL::deallocate(p);}};
}
可以看到,allocate和deallocate分别调用的是::operator new和::operator delete,这是我第一次见到这种用法,网上查找资料可以知道,他们实际上是两个运算符,是调用的malloc和free在堆上申请、释放空间。new一个对象分为三个步骤,调用::operator new分配空间,构造对象和返回指针,一般来说我们不会去重载new运算符,用的是全局的。其他的相关内容可以参考new详解。
STL源码剖析-Allocator相关推荐
- STL源码剖析学习二:空间配置器(allocator)
STL源码剖析学习二:空间配置器(allocator) 标准接口: vlaue_type pointer const_pointer reference const_reference size_ty ...
- 《STL源码剖析》笔记——allocator
六大组件间关系 部分STL文件包含关系 allocator包含于中: 实际实现于三个文件 : 1.stl_construct.h :对象的构造和析构 2.stl_alloc.h空间配置和释放 3.st ...
- 【《STL源码剖析》提炼总结】 第1节:空间配置器 allocator
文章目录 一. 什么是空间配置器 二. STL allocator的四个操作: allocate,deallocate,construct,destroy `construct()` `destroy ...
- STL源码剖析 map
所有元素会根据元素的键值自动被排序 元素的类型是pair,同时拥有键值和实值:map不允许两个元素出现相同的键值 pair 代码 template <class T1,class T2> ...
- STL源码剖析 slist单向链表概述
概述 SGI STL的list是一个双向链表,单向链表是slist,其不在标准规格之内 单向和双向链表的区别在于,单向链表的迭代器是单向的 Forward Iterator,双向链表的迭代器属于双向的 ...
- STL源码剖析 空间配置器 查漏补缺
ptrdiff_t含义 减去两个指针的结果的带符号整数类型 ptrdiff_t (Type support) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 std::set_new_ ...
- 《STL源码剖析》相关面试题总结
一.STL简介 STL提供六大组件,彼此可以组合套用: 容器 容器就是各种数据结构,我就不多说,看看下面这张图回忆一下就好了,从实现角度看,STL容器是一种class template. 算法 各种常 ...
- C++ STL源码剖析 笔记
写在前面 记录一下<C++ STL源码剖析>中的要点. 一.STL六大组件 容器(container): 各种数据结构,用于存放数据: class template 类泛型: 如vecto ...
- SGI STL源码剖析——空间配置器
SGI STL源码剖析--空间配置器 前言 空间配置器 SGI空间配置器 内存配置和对象构造 构造和析构 空间的配置和释放 第一级配置器 第二级配置器 空间配置 重新填充 重中之重的内存池 前言 ...
最新文章
- python中def fun(a、b=200)_python中的函数的参数和可变参数
- 简述enumerate函数
- GridView 72般绝技
- /etc/services
- lua ue_slua unreal分析(二)LuaActor与lua表互访
- MySQL 8.0.22执行器源码分析HashJoin —— BuildHashTable函数细节步骤
- [css] rgba()和opacity这两个的透明效果有什么区别呢?
- python语法基础知识案例_Python 语法速览与实战清单
- PAT乙级(1028 人口普查)
- 顶顶通软电话介绍-一个网络电话客户端(SIP软电话)
- 如何写出高分IB TOK Essay?
- Enhancing Social Recommendation with Adversarial Graph Convolutional Networks个人笔记
- 十大蓝牙耳机排名,2021性价比蓝牙耳机
- 详解开关电源RCD钳位电路工作过程
- jQuery(javascript) 与Vue有什么区别
- 新颖的自我介绍_有关最新颖的自我介绍
- 阿昌教你使用谷歌guava工具包---集合包
- [转]Berkeley DB实现分析
- 2008大连市政府挂牌督办48项工作圆满实现全年目标
- 10.0 自定义SystemUI下拉状态栏和通知栏视图(八)之锁屏通知布局