STL内存管理

其实对于一个标准的STL 容器,当vector vec 的真实语句应该是 vetor<int, allocator>vec,allocator是一个标准的配置器,其作用就是为各个容器管理内存。这里需要注意的是在SGI STL中,有两个配置器:allocator(标准的)和alloc(默认的配置器)

注意: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有两阶段操作:

  1. 调用:: operator new配置内存
  2. 调用:: placement new构造对象
    delete也有两阶段操作:
  3. 调用析构函数析构对象
  4. 调用:: operator delete释放内存

image

  • stl_construct.h:管理构造和析构
  • stl_allo.h:内存的申请和释放
  • stl_uninitialized.h:大片内存的申请和复制

一级配置器和二级配置器的关系如下:
image

malloc_alloc_template

在SGI STL中,如果申请的内存区域大于128B的时候,就会调用一级适配器,而一级适配器的调用也是非常简单的,直接用malloc申请内存,用free释放内存。但是malloc和free是C语言接口,为了模拟C++内存不足,另外提供了__set_malloc_handler,用来内存不足时回调

当内存不足的时候,处理程序会不断地调用malloc_handler,但如果malloc_handler并未设定,就会直接抛出bad_alloc异常。

default_alloc_template

二级内存分配器,内部是一个内存池

image

二级配置器每次申请一大块内存,并由内部内存池和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内存池获取资源处理逻辑

  1. refiil向内存池申请20个对象大小的资源
    1. 内存池有足够的资源配置20个对象,直接返回
    2. 内存池不足以配置20个对象,返回能够配置的最大个数
    3. 内存池一个对象都不能配置
      1. 首先将内存池剩余的资源给合适的free_list
      2. 调用malloc获取资源
        1. malloc失败,从free_list收集资源,仍然失败,调用一级配置器,希望oom handler会处理
        2. malloc成功,返回资源
  2. 将获取的资源形成链表

这里有个例子:

  1. chunk_alloc(32,20),调用malloc申请40 * 32块资源。20个交给free_list,20个留给内存池
  2. chunk_alloc(64,20),返回10 * 64
  3. 万一malloc失败了,从free_list获取内存空间抢救一下。
  4. 如果free_list找不到,只能让一级配置器的new handler抢救了

释放资源逻辑

释放资源,首先从容器获取要释放对象的大小(这里和free不同,free并不需要知道),然后放入free_list,大家注意,居然不调用free。这样的话,随着这个alloc的析构(好像只有进程结束),里面申请的内存才回释放

未初始化空间的构造和析构

一共有5个函数干这事,除了construct,其他的函数都会判定构造或者析构函数是否是trival,然后进行优化。

construct

负责处理对象的构造,不会判定是否是POD

destory

负责对象的析构,会对是否trival进行优化
image

uninitialized_copy

对象的批量拷贝

uninitialized_fill

负责对象批量填充

uninitialized_fill_n

负责对象的批量填充

image

小技巧(将内存上调到8的倍数)

int round_up(int bytes){
    return ((bytes + ALIGN - 1) & ~(ALIGN - 1));
}
posted on 2017-05-27 15:54  bitError  阅读(441)  评论(0编辑  收藏  举报