> 文章列表 > STL源码剖析-Allocator

STL源码剖析-Allocator

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类型为什么还要用模板呢?

__first_stage_alloc中给出的是静态函数接口,所以callback/handler也得是静态的形式,但是我想让不同的Allocator拥有不同的callback/handler,那就只有定义多个Allocator类了(Allocator1,Allocator2,Allocator3......)。有没有办法可以解决这个问题?

template<int __inst>就可以解决上面的问题。我们用不同的参数显式实例化__first_stage_alloc,拿到的静态成员地址是不同的,所以我们就可以给他们注册不同的callback/handler函数。

template TinySTL::__first_stage_alloc<0>;
template TinySTL::__first_stage_alloc<1>;

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进行限制,超出了地址范围是否有处理呢?

后续看到了再来解答.