其实对于一个标准的STL 容器,当vector
注意:alloc(带poll)在3.3版本之前是默认的内存分配器,而allocator(3.4以后的版本)才是默认的内存分配器。所以,候杰的STL源码剖析过时了,它讲的只是3.3以前的版本
allocator
allocator不建议使用,它的效率不佳,只是对:: operator new和:: operator delete做了一层简单的封装而已
#include <new>// for new
#include <cstddef> // size_t
#include <climits> // for unit_max
#include <iostream> // for cerr
using namespace std;
namespace SLD {
template <class 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;
template <class U>
struct rebind
{
typedef allocator<U> other;
};
//申请内存
pointer allocate(size_type n, const void* hint = 0)
{
T* tmp = (T*)(::operator new((size_t)(n * sizeof(T))));
//operator new 和new operator是不同的
if (!tmp)
cerr << "out of memory"<<endl;
return tmp;
}
//释放内存
void deallocate(pointer p)
{
::operator delete(p);
}
//构造
void construct(pointer p, const T& value)
{
new(p) T1(value);
}
//析构
void destroy(pointer p)
{
p->~T();
}
//取地址
pointer address(reference x)
{
return (pointer)&x;
}
const_pointer const_address(const_reference x)
{
return (const_pointer)&x;
}
size_type max_size() const
{
return size_type(UINT_MAX/sizeof(T));
}
};
}
SGI的std::alloc(全局共用一个内存池)
在我们敲下如下代码:
Foo *pf = new Foo;
delete pf;
其中的new有两阶段操作:
- 调用:: operator new配置内存
- 调用:: placement new构造对象
delete也有两阶段操作: - 调用析构函数析构对象
- 调用:: operator delete释放内存
- stl_construct.h:管理构造和析构
- stl_allo.h:内存的申请和释放
- stl_uninitialized.h:大片内存的申请和复制
一级配置器和二级配置器的关系如下:
malloc_alloc_template
在SGI STL中,如果申请的内存区域大于128B的时候,就会调用一级适配器,而一级适配器的调用也是非常简单的,直接用malloc申请内存,用free释放内存。但是malloc和free是C语言接口,为了模拟C++内存不足,另外提供了__set_malloc_handler,用来内存不足时回调
当内存不足的时候,处理程序会不断地调用malloc_handler,但如果malloc_handler并未设定,就会直接抛出bad_alloc异常。
default_alloc_template
二级内存分配器,内部是一个内存池
二级配置器每次申请一大块内存,并由内部内存池和free_list管理。如果用户有同样的需求,直接从free_list取出,同样的,将用户归还的内存放入free_list中。维护了16根不同大小的free_list,分别是8,16,24...128。free_list的节点构造如下:
union obj{
union obj *free_list_link;
char client_data[1];
}
通过这种方式,我们的free_list链表不会占用额外内存。(和tcmalloc的freelist一摸一样啊)
二级配置器函数申请资源逻辑
- 寻找合适的free_list
- free_list非空,返回链表中第一块资源
- free_list空,调用refill从内存池填充链表,返回第一块资源
refill从调用chunk_alloc内存池获取资源处理逻辑
- refiil向内存池申请20个对象大小的资源
- 内存池有足够的资源配置20个对象,直接返回
- 内存池不足以配置20个对象,返回能够配置的最大个数
- 内存池一个对象都不能配置
- 首先将内存池剩余的资源给合适的free_list
- 调用malloc获取资源
- malloc失败,从free_list收集资源,仍然失败,调用一级配置器,希望oom handler会处理
- malloc成功,返回资源
- 将获取的资源形成链表
这里有个例子:
- chunk_alloc(32,20),调用malloc申请40 * 32块资源。20个交给free_list,20个留给内存池
- chunk_alloc(64,20),返回10 * 64
- 万一malloc失败了,从free_list获取内存空间抢救一下。
- 如果free_list找不到,只能让一级配置器的new handler抢救了
释放资源逻辑
释放资源,首先从容器获取要释放对象的大小(这里和free不同,free并不需要知道),然后放入free_list,大家注意,居然不调用free。这样的话,随着这个alloc的析构(好像只有进程结束),里面申请的内存才回释放
未初始化空间的构造和析构
一共有5个函数干这事,除了construct,其他的函数都会判定构造或者析构函数是否是trival,然后进行优化。
construct
负责处理对象的构造,不会判定是否是POD
destory
负责对象的析构,会对是否trival进行优化
uninitialized_copy
对象的批量拷贝
uninitialized_fill
负责对象批量填充
uninitialized_fill_n
负责对象的批量填充
小技巧(将内存上调到8的倍数)
int round_up(int bytes){
return ((bytes + ALIGN - 1) & ~(ALIGN - 1));
}