一、第一级配置器
顾名思义,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 align
static 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;
// 使用模板类对象,需要在类名前面使用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<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 pool
size_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) {
// 如果分配失败,则从其他链表中先获取一块buffer
size_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块buffer
int __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); // 0xb8fb2c
printf("%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详解。