#ifndef KNIFE_ALLOC_H
#define KNIFE_ALLOC_H
#include "config.h"
#include "knifeio.h"
#include <cstdlib>
#include <cstddef>
// 通常写注释会中英混杂,其实我英文也不是很好,主要是懒得切换输入法了,就当练练口语交际
// 这次实现的就是STL的重要部分了:allocator
// 仍旧是SGI_STL 203的实现方法,略微有一些不一样,加上了自己觉得合理的部分
// 整体框架是这样的,首先将alloc的使用情况分为release和debug两种,于是
// 具体实现还是得看后面的代码注释和侯捷老师的书哦
// Release : User -> simple_alloc -> if (n > 128) malloc_alloc / oom_malloc_alloc_template : alloc_template
// else list_alloc : alloc_template /
// hash_alloc : alloc_template /
// bitmap_alloc : alloc_template
// Debug : User -> simple_alloc -> debug_alloc
// 在这里有一个包装的问题,原文中使用simple_aloc将所有的template问题给解决了
// 每个实现只需要保持返回void*就可以了,但我的设计有点弄巧成拙的意思,看来还要多学习
_STL_NAMESPACE_BEGIN
#define __EXIT_BAD_ALLOC 0xbada110c //badalloc
#define _THROW_BAD_ALLOC knife::printf("Out of memory while alloc"); exit(__EXIT_BAD_ALLOC)
#define _THROW_BAD_REALLOC knife::printf("Out of memory while realloc"); exit(__EXIT_BAD_ALLOC)
// default global out_of_memory handler function
void def_oom_handler()
{
knife::printf("Out of memory");
}
// This function is used to fill the buffer memory
// in some system, if we do NOT initialize the buffer memory
// OS would NOT 'really' assign the memory to us
// Really we should use double or long to acculate,
// but we can not assume that the size is 4 align
template <typename T, typename _Size>
void memfill(T* ptr, _Size n)
{
char* p = (char*) ptr;
while (n--) *p++ = 0;
}
// 这个malloc是一个借助C库的非常“正常”的allocator
// 比较独特的地方在于,他使用一个函数指针,处理out_of_memory的情况
// 同时允许用户指定这个指针,并使用用户指定的函数指针去处理这个error
// 因为这是类库,并不负责处理用户的内存问题,那种情况太多太复杂了
// 我们需要懂得将这种“太过”普适性的问题延迟到用户端,即使用者端
// 提供给使用者解决这个问题的接口,让他们能够解决这个问题就可以了
// 而且我们可以通过包装这个接口,对用户提供的方案设计的范围加以限制
// 但这里还要考虑到用户使用接口的延时性问题,即用户会在可能碰到这个问题
// 的地方之后几条语句再使用这个接口去处理问题,这会导致“当时”接口还是空的
class malloc_alloc {
private:
static void* oom_malloc(size_t);
static void* oom_realloc(void*, size_t);
static void (* _malloc_oom_handler)();
public:
static void* allocate(size_t n);
static void deallocate(void* p, size_t /*n*/);
static void* reallocate(void* p, size_t /*old_sz*/, size_t new_sz);
static void (* set_malloc_handler(void (*f)())) ();
};
// Loop the oom_handler until we can malloc the n size memory
// 原文因为考虑到true的版本问题,使用了for(;;), 为了可读性
// 这里还是使用了while (true)
void* malloc_alloc::oom_malloc(size_t n)
{
void* pointer = NULL;
while (true)
{
if (NULL == _malloc_oom_handler) _THROW_BAD_ALLOC;
_malloc_oom_handler();
pointer = malloc(n);
if (NULL != pointer) return pointer;
}
}
void* malloc_alloc::oom_realloc(void* p, size_t new_sz)
{
void* pointer = NULL;
while (true)
{
if (NULL == _malloc_oom_handler) _THROW_BAD_ALLOC;
_malloc_oom_handler();
pointer = realloc(p, new_sz);
if (NULL != pointer) return pointer;
}
}
void (* malloc_alloc::_malloc_oom_handler)() = 0;
void* malloc_alloc::allocate(size_t n)
{
void* pointer = malloc(n);
knife::printf(n, pointer);
if (NULL == pointer) oom_malloc(n);
return pointer;
}
void malloc_alloc::deallocate(void* p, size_t /* n */)
{
free(p);
}
void* malloc_alloc::reallocate(void* p, size_t /*old_sz*/, size_t new_sz)
{
void* pointer = realloc(p, new_sz);
if (NULL == pointer) oom_realloc(p, new_sz);
return pointer;
}
void(* malloc_alloc::set_malloc_handler(void (*f)())) ()
{
void (*old)() = _malloc_oom_handler;
_malloc_oom_handler = f;
return old;
}
template <typename T>
class alloc_template {
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
};
// 应该还会有malloc的template版本,我加上了
// This version is sooo simple that you should not
// suppose what will it do while something wrong happened
// If some bad things real happened while the user
// use allocator of this version, The things we can only do
// is to throw a exception or just report this error
// and shut down
template <typename T>
class malloc_alloc_template : public alloc_template<T>{
public:
static typename alloc_template<T>::pointer
allocate(typename alloc_template<T>::size_type);
static void
deallocate(typename alloc_template<T>::pointer);
static typename alloc_template<T>::pointer
reallocate(typename alloc_template<T>::pointer,
typename alloc_template<T>::size_type);
};
template <typename T>
typename alloc_template<T>::pointer
malloc_alloc_template<T>::allocate(typename alloc_template<T>::size_type n)
{
typename alloc_template<T>::pointer p = (typename alloc_template<T>::pointer) malloc(n);
if (NULL != p) return p;
else _THROW_BAD_ALLOC;
}
template <typename T>
void
malloc_alloc_template<T>::deallocate(typename alloc_template<T>::pointer p)
{
free(p);
}
template <typename T>
typename alloc_template<T>::pointer
malloc_alloc_template<T>::reallocate(typename alloc_template<T>::pointer p,
typename alloc_template<T>::size_type new_sz)
{
typename alloc_template<T>::pointer tmp = (typename alloc_template<T>::pointer) realloc(p, new_sz);
if (NULL != tmp) return tmp;
else _THROW_BAD_ALLOC;
}
template <typename T>
class oom_malloc_alloc_template : alloc_template<T>{
public:
typedef void(*OOM_Handler)();
static typename alloc_template<T>::pointer allocate(typename alloc_template<T>::size_type);
static void deallocate(typename alloc_template<T>::pointer);
static typename alloc_template<T>::pointer reallocate(typename alloc_template<T>::pointer,
typename alloc_template<T>::size_type);
static OOM_Handler set_oom_handler(OOM_Handler);
protected:
static void oom_malloc(typename alloc_template<T>::pointer);
static void oom_realloc(typename alloc_template<T>::pointer,
typename alloc_template<T>::size_type);
private:
static OOM_Handler CUR_HANDLER;
};
template <typename T>
typename oom_malloc_alloc_template<T>::OOM_Handler
oom_malloc_alloc_template<T>::CUR_HANDLER = def_oom_handler;
template <typename T>
typename alloc_template<T>::pointer
oom_malloc_alloc_template<T>::allocate(typename alloc_template<T>::size_type n)
{
typename alloc_template<T>::pointer p = NULL;
p = (typename alloc_template<T>::pointer) malloc(n);
if (NULL == p) oom_malloc_alloc_template<T>::oom_malloc(p);
return p;
}
template <typename T>
void
oom_malloc_alloc_template<T>::deallocate(typename alloc_template<T>::pointer p)
{
free(p);
}
template <typename T>
typename alloc_template<T>::pointer
oom_malloc_alloc_template<T>::reallocate(typename alloc_template<T>::pointer p,
typename alloc_template<T>::size_type n)
{
typename alloc_template<T>::pointer tmp_pointer = NULL;
tmp_pointer = (typename alloc_template<T>::pointer) realloc(p, n);
if (NULL == tmp_pointer) oom_malloc_alloc_template<T>::oom_realloc(p, n);
return tmp_pointer;
}
template <typename T>
typename oom_malloc_alloc_template<T>::OOM_Handler
oom_malloc_alloc_template<T>::set_oom_handler(OOM_Handler new_handler)
{
OOM_Handler old_handler = CUR_HANDLER;
CUR_HANDLER = new_handler;
return old_handler;
}
template <typename T>
void
oom_malloc_alloc_template<T>::oom_malloc(typename alloc_template<T>::pointer p)
{
typename alloc_template<T>::pointer tmp_pointer;
while (true)
{
CUR_HANDLER();
tmp_pointer = allocate(p);
if (tmp_pointer != NULL) return;
}
}
template <typename T>
void
oom_malloc_alloc_template<T>::oom_realloc(typename alloc_template<T>::pointer p,
typename alloc_template<T>::size_type new_sz)
{
typename alloc_template<T>::pointer tmp_pointer;
while (true)
{
CUR_HANDLER();
tmp_pointer = reallocate(p, new_sz);
if (tmp_pointer != NULL) return;
}
}
// This is just a copy of the original one,
// cause this simple_alloc is simple enough
template<typename T, typename Alloc>
class simple_alloc {
public:
static T *allocate(size_t n)
{ return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
static T *allocate(void)
{ return (T*) Alloc::allocate(sizeof (T)); }
static void deallocate(T *p, size_t n)
{ if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
static void deallocate(T *p)
{ Alloc::deallocate(p, sizeof (T)); }
};
// 实际上我之前对alloc的看法是错误的,准确的说,是太简单了
// 我以为alloc只是使用了嵌入式指针,既减少内存占用,又同时
// 让内存池能够用链表连接起来,后来发现不是的,其实他是将
// 小于_MAX_BYTES即128的内存分配视作小内存分配,这种分配容易
// 造成内存碎片,他给出的解决方法是,用8 bytes做一个align,
// 每个小内存都会被align成一整块(一个ROUND_UP(N)),然后分配出去
// 这样就减少内存占用了,同时优化的还有一次性malloc一大块内存
// 这样也会减少内存碎片(既优化操作系统内存调度的速度,又减少了
// 内存资源的小号),当然在SGI的作者看来,最少是320一次才不容
// 易造成碎片
class list_alloc {
public:
static void* allocate(size_t);
static void deallocate(void*, size_t);
static void* reallocate(void*, size_t);
private:
union obj {
union obj* list_link;
char data[1];
};
// 最小支持8bytes,更小的部分不会分配
static const size_t __ALIGN = 8;
static const size_t __MAX_BYTES = 128;
static const int __LIST_SIZE = __MAX_BYTES / __ALIGN;
static obj* _LIST_POOL[__LIST_SIZE];
static char* list_begin;
static char* list_end;
static size_t heap_size;
static size_t ROUND_UP(size_t);
static size_t LIST_POS(size_t);
static char* chunk_alloc(size_t, int&);
static void* refill(size_t);
};
list_alloc::obj*
list_alloc::_LIST_POOL[__LIST_SIZE] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
char* list_alloc::list_begin = 0;
char* list_alloc::list_end = 0;
size_t list_alloc::heap_size = 0;
void* list_alloc::allocate(size_t n)
{
if (n > __MAX_BYTES)
{
return malloc_alloc::allocate(n);
}
// 获得要分配的list上存储的指针
obj** list = _LIST_POOL + LIST_POS(n);
obj* result = *list;
if (result == 0)
{
return refill(ROUND_UP(n));
}
*list = result->list_link;
return result;
}
void list_alloc::deallocate(void* ptr, size_t n)
{
obj* q = (obj*) ptr;
obj** list_ptr = NULL;
if (n > __MAX_BYTES)
{
malloc_alloc::deallocate(ptr, n);
return;
}
list_ptr = _LIST_POOL + LIST_POS(n);
q->list_link = *list_ptr;
*list_ptr = q;
}
size_t list_alloc::ROUND_UP(size_t n)
{
return (n + __ALIGN - 1) & ~(__ALIGN + 1);
}
// 虽然我们会将ROUND_UP之后的数传入进来
// 但这个函数不应该将ROUND_UP作为前提
// 这是违反实现原则的
size_t list_alloc::LIST_POS(size_t n)
{
return (n + __ALIGN - 1) / __ALIGN - 1;
}
// alloc a chunk of memory which costs n * nobjs bytes
// Before we alloc the mem, we will check the mem we have
char* list_alloc::chunk_alloc(size_t bytes, int& nobjs)
{
char* result = NULL;
size_t bytes_need = bytes * nobjs;
size_t bytes_left = list_end - list_begin;
// 如果剩下的空间比需要的大,那么直接返回list_begin
// 并且修改list_begin即可
if (bytes_left > bytes_need)
{
result = list_begin;
list_begin += bytes_need;
return result;
}
// 如果剩下的空间必须要的小,但是剩下的空间又够分配
// 一个以上的bytes,那么修改nobjs的值为剩下空间够分
// 配的bytes数量,并且返回list_begin,并修改list_begin
// 的值
else if (bytes_left > bytes)
{
nobjs = bytes_left / bytes;
bytes_need = bytes * nobjs;
result = list_begin;
list_begin += bytes_need;
return result;
}
// 如果不剩下任何空间了(或者是剩下的空间不够一个bytes)
// 那么我们重新malloc一块内存,大小为2倍于需求的大小
// 并调整list_begin list_end heap_size的值,让list_begin
// 和list_end分别指向重新非配出来的值,再重新调用一次
// chunk_alloc
else
{
size_t bytes_to_alloc = 2 * bytes_need + ROUND_UP(heap_size);
// 还会剩下一些空间,为防止内存碎片,
// 我们把它们分配到适合的位置去
if (bytes_left > 0)
{
obj** list_ptr = _LIST_POOL + LIST_POS(bytes_left);
((obj*) list_begin)->list_link = *list_ptr;
*list_ptr = (obj*) list_begin;
}
list_begin = (char*) malloc(bytes_to_alloc);
if (0 == list_begin)
{
int i = bytes;
obj** list_ptr;
obj* p;
for (; i <= __MAX_BYTES; i += __ALIGN)
{
list_ptr = _LIST_POOL + LIST_POS(i);
p = *list_ptr;
if (0 != p)
{
*list_ptr = p -> list_link;
list_begin = (char*) p;
list_end = list_begin + i;
return (chunk_alloc(bytes, nobjs));
}
}
}
list_end = list_begin + bytes_to_alloc;
heap_size += bytes_to_alloc;
return chunk_alloc(bytes, nobjs);
}
}
// 如何在list中分配memory的逻辑是由chunk_alloc
// 负责的,当nobjs == 1时,代表只分配了一个obj
// 这正是refill所要做的,
//
//
void* list_alloc::refill(size_t n)
{
static int OBJ_NUM = 20;
int nobjs = OBJ_NUM;
char* chunk = chunk_alloc(n, nobjs);
obj **list_ptr, *result;
obj *curr_obj, *next_obj;
if (1 == nobjs) return chunk;
list_ptr = _LIST_POOL + LIST_POS(n);
result = (obj*) chunk;
*list_ptr = next_obj = (obj*) (chunk + n);
for (int i = 1; ;++i)
{
curr_obj = next_obj;
next_obj = (obj*) ((char*)next_obj + n);
if (nobjs - 1 == i)
{
curr_obj->list_link = 0;
break;
}
else
{
curr_obj->list_link = next_obj;
}
}
return result;
}
// 利用bitmap作出的alloc,等实现了bitmap再说吧。。。
template <class T>
class bitmap_alloc : public alloc_template<T> {
public:
};
// 利用hash_table的技术,将内存池做成hash_table
// 可能在有时会比较有效吧,不过如果要用的话尽可以都试试
// 做个试验,就知道最适合自己项目的版本了
template <class T>
class hash_alloc : public alloc_template<T> {
public:
};
_STL_NAMESPACE_END
#endif // KNIFE_ALLOC_H
【试着自己写一个STL 】Knife_STL —— alloc.h
最新推荐文章于 2021-04-15 23:44:53 发布