一、第一级配置器

顾名思义,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相关推荐

  1. STL源码剖析学习二:空间配置器(allocator)

    STL源码剖析学习二:空间配置器(allocator) 标准接口: vlaue_type pointer const_pointer reference const_reference size_ty ...

  2. 《STL源码剖析》笔记——allocator

    六大组件间关系 部分STL文件包含关系 allocator包含于中: 实际实现于三个文件 : 1.stl_construct.h :对象的构造和析构 2.stl_alloc.h空间配置和释放 3.st ...

  3. 【《STL源码剖析》提炼总结】 第1节:空间配置器 allocator

    文章目录 一. 什么是空间配置器 二. STL allocator的四个操作: allocate,deallocate,construct,destroy `construct()` `destroy ...

  4. STL源码剖析 map

    所有元素会根据元素的键值自动被排序 元素的类型是pair,同时拥有键值和实值:map不允许两个元素出现相同的键值 pair 代码 template <class T1,class T2> ...

  5. STL源码剖析 slist单向链表概述

    概述 SGI STL的list是一个双向链表,单向链表是slist,其不在标准规格之内 单向和双向链表的区别在于,单向链表的迭代器是单向的 Forward Iterator,双向链表的迭代器属于双向的 ...

  6. STL源码剖析 空间配置器 查漏补缺

    ptrdiff_t含义 减去两个指针的结果的带符号整数类型 ptrdiff_t (Type support) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 std::set_new_ ...

  7. 《STL源码剖析》相关面试题总结

    一.STL简介 STL提供六大组件,彼此可以组合套用: 容器 容器就是各种数据结构,我就不多说,看看下面这张图回忆一下就好了,从实现角度看,STL容器是一种class template. 算法 各种常 ...

  8. C++ STL源码剖析 笔记

    写在前面 记录一下<C++ STL源码剖析>中的要点. 一.STL六大组件 容器(container): 各种数据结构,用于存放数据: class template 类泛型: 如vecto ...

  9. SGI STL源码剖析——空间配置器

    SGI STL源码剖析--空间配置器 前言 空间配置器 SGI空间配置器 内存配置和对象构造 构造和析构 空间的配置和释放 第一级配置器 第二级配置器 空间配置 重新填充 重中之重的内存池 前言    ...

最新文章

  1. python中def fun(a、b=200)_python中的函数的参数和可变参数
  2. 简述enumerate函数
  3. GridView 72般绝技
  4. /etc/services
  5. lua ue_slua unreal分析(二)LuaActor与lua表互访
  6. MySQL 8.0.22执行器源码分析HashJoin —— BuildHashTable函数细节步骤
  7. [css] rgba()和opacity这两个的透明效果有什么区别呢?
  8. python语法基础知识案例_Python 语法速览与实战清单
  9. PAT乙级(1028 人口普查)
  10. 顶顶通软电话介绍-一个网络电话客户端(SIP软电话)
  11. 如何写出高分IB TOK Essay?
  12. Enhancing Social Recommendation with Adversarial Graph Convolutional Networks个人笔记
  13. 十大蓝牙耳机排名,2021性价比蓝牙耳机
  14. 详解开关电源RCD钳位电路工作过程
  15. jQuery(javascript) 与Vue有什么区别
  16. 新颖的自我介绍_有关最新颖的自我介绍
  17. 阿昌教你使用谷歌guava工具包---集合包
  18. [转]Berkeley DB实现分析
  19. 2008大连市政府挂牌督办48项工作圆满实现全年目标
  20. 10.0 自定义SystemUI下拉状态栏和通知栏视图(八)之锁屏通知布局

热门文章

  1. python--爬虫--爬虫学习路线指南
  2. LeetCode-55、跳跃游戏-中等
  3. Joystick Shield 连接 nRF24L01
  4. 电机仿真系列-基于LabVIEW的电机测试系统研究
  5. 蓝牙芯片|伦茨科技-智能蓝牙BLE5.2芯片ST17H66
  6. 多模态分析数据集(Multimodal Dataset)整理
  7. TiDB 在零氪科技(LinkDoc)大数据医疗系统的实践
  8. iphone浏览器Safari中的数字被识别为电话号码
  9. FPGA(一)Vivado与ModelSim
  10. GitHub——Gist