C++STL学习(13)STL深入(2) SGI STL空间配置器

注:博客内容均来自于对《STL源码剖析》侯捷,华中科技大学出版社一书的笔记。转载请注明出处。
所有例程在Dev-C++上编译运行,编译选择的是GUN C++11。


1、SGISTL空间配置器



C++STL学习(13)STL深入(2) SGI STL空间配置器_第1张图片


2、SGI的标准空间配置器



SGI的标准空间配置器很上一篇博客中的空间配置器大体上基本一样。可以对比看两者的实现上一篇
// 开头的这段声明足以看出,SGI做的这个版本仅仅是为了在必须考虑向下兼容
// 的时候才会使用,它的其他文件中根本不会用到这个!
// DO NOT USE THIS FILE unless you have an old container implementation
// that requires an allocator with the HP-style interface.  SGI STL
// uses a different allocator interface.  SGI-style allocators are not
// parametrized with respect to the object type; they traffic in void *
// pointers.  This file is not included by any other SGI STL header.

#ifndef DEFALLOC_H
#define DEFALLOC_H

#include 
#include 
#include 
#include 
#include 
#include  //

template 
inline T* allocate(ptrdiff_t size, T*) 
{
    set_new_handler(0);
	//和我们自己设计的空间配置器基本都是一致的
    T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
    if (tmp == 0) 
	{
		cerr << "out of memory" << endl; 
		exit(1);
    }
    return tmp;
}


template 
inline void deallocate(T* buffer) 
{
    ::operator delete(buffer);
}

template 
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 ::allocate((difference_type)n, (pointer)0);
    }
	
    void deallocate(pointer p) 
	{ ::deallocate(p); }
    
	pointer address(reference x) 
	{ return (pointer)&x; }
    
	const_pointer const_address(const_reference x) 
	{ 
		return (const_pointer)&x; 
    }
	
	//这个成员函数之前我们没有设计过
    size_type init_page_size() 
	{ 
		return max(size_type(1), size_type(4096/sizeof(T))); 
    }
	
    size_type max_size() const 
	{ 
		return max(size_type(1), size_type(UINT_MAX/sizeof(T))); 
    }
};

class allocator 
{
public:
    typedef void* pointer;
};

#endif


3、SGI自己的空间配置器


通常,C++的内存配置操作和释放操作是这样的:

class Foo{};

Foo *pf = new Foo;  //配置内存,然后构造对象

delete pf;                  //析构对象,然后释放内存

上述过程可以描述成:

1、配置内存(new

2、构造对象Foo::Foo()

3、析构对象Foo::~Foo()

4、释放内存(delete

SGI自己的内存配置器中将这个四个过程分开

1、配置内存(new-----  alloc::allocate()

2、构造对象Foo::Foo()  ----- ::construct()

3、析构对象Foo::~Foo() ----- ::destroy()

4、释放内存(delete----- ::deallocate()



C++STL学习(13)STL深入(2) SGI STL空间配置器_第2张图片





3.1 construct()destory()


   C++STL学习(13)STL深入(2) SGI STL空间配置器_第3张图片





3.2   *空间的配置和释放

考虑到小区块可能造成的内存破碎问题,SGI设计了双层的配置器*。下图所示:



C++STL学习(13)STL深入(2) SGI STL空间配置器_第4张图片

第一级配置器直接使用malloc()free();

第二级配置器则复杂的多,它会视不同的情况采取不同的策略(这是提升效率的关键)

采取的策略是:

1、当配置的区块超过128bytes时,视为“足够大”,就调用第一级配置器;

2、当配置的区块小于128bytes的时候,就认为比较小,为了降低额外负担就采用复杂的memory pool整理的方式,便不再求助于第一级配置器。



3.3 第一级配置器

//没有template型别参数
template 
class __malloc_alloc_template 
{
private:
	static void *oom_malloc(size_t);
	static void *oom_realloc(void *, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
    static void (* __malloc_alloc_oom_handler)();
#endif

public:
	static void * allocate(size_t n)
	{
		//第一级配置器直接使用 malloc()
		void *result = malloc(n);	
		
		//分配失败则调用out of memory handling
		if (0 == result) result = oom_malloc(n);   
		return result;
	}

	static void deallocate(void *p, size_t /* n */)
	{
		free(p);	// 第一级配置器直接使用 free()
	}

	static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
	{
		// 第一级配置器直接使用 realloc()
		void * result = realloc(p, new_sz);	
		//分配失败则调用out of memory handling
		if (0 == result) result = oom_realloc(p, new_sz);
		return result;
	}

	//类似 set_new_handler() 
	//因为使用的是C版本的malloc realloc所以不能直接使用C++的set_new_handler()机制
	static void (* set_malloc_handler(void (*f)()))()
	{
		void (* old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = f;
		return(old);
	}

};

// malloc_alloc out-of-memory handling
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template 
void (* __malloc_alloc_template::__malloc_alloc_oom_handler)() = 0;
#endif

template 
void * __malloc_alloc_template::oom_malloc(size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {	//不断尝试释放->配置->释放->...
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*my_malloc_handler)();		// 调用处理例程,企图释放内存
        result = malloc(n);			// 再次尝试配置内存
        if (result) return(result);
    }
}

template 
void * __malloc_alloc_template::oom_realloc(void *p, size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {	//不断尝试释放->配置->释放->...
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*my_malloc_handler)();	// 调用处理例程,企图释放内存
        result = realloc(p, n);	// 再次尝试配置内存
        if (result) return(result);
    }
}

typedef __malloc_alloc_template<0> malloc_alloc;


3.5 第二级配置器


    第二级配置器相比第一级配置器复杂的多。它多了一些机制,避免太多小额区造成内存碎片。

SGI的做法:

--如果区块足够大(超过128bytes)移交给第一级配置器处理;

--区块小于128bytes,以内存池的方式进行管理。

   上述过程也叫做级次配置:每次配置一大块内存,并维护对应该内存的自由链表。为了方便管理SGI的空间配置器会主动将任何内存需求量上调到8的倍数,并维护16个自由链表,他们各自管理的大小分别是:8,16,24 128 bytes 的小额区块。

自由链表的结构如下:

union obj

{

  union obj * free_list_link;

 char client_data[1];

};

    这样的设计有一个很精妙的地方就是不用为了维护链表而去造成内存的浪费(通常链表中保留一个指针来维护该链表)。但是这里设计成联合体的形式,当该区块的内存还没有被使用的时候,obj就指向下一个区块,当该区块使用的时候,就对client_data赋值,这个赋值的操作会造成obj内容的改变,也就是说这个区块就从该链表中被移除来了。

C++STL学习(13)STL深入(2) SGI STL空间配置器_第5张图片
  enum {__ALIGN = 8};			// 小型区块的上调边界
  enum {__MAX_BYTES = 128};		// 小型区块的上限
  enum {__NFREELISTS = __MAX_BYTES/__ALIGN};	// free-lists 个数
#endif

//没有
template 
class __default_alloc_template 
{
private:
	//上调至8的倍数
	static size_t ROUND_UP(size_t bytes) 
	{
		//tips:数字n上调至x的倍数可以使用(n + x - 1) & ~(x - 1)
		//先将数字上调,然后将低位变成0就好了(二进制情况)
		return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
	}

private:
	union obj    //free-lists结点构造
	{
		union obj * free_list_link;
		char client_data[1];    /* The client sees this. */
	};
  
private:
    //__NFREELISTS个free-lists结点
    static obj * __VOLATILE free_list[__NFREELISTS]; 
	
	//传入区块大小,决定使用第n号free-list, n从0号开始算起
	static  size_t FREELIST_INDEX(size_t bytes) 
	{
		return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
	}

	//返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list
	static void *refill(size_t n);
	
	//配置一大块空间,可容纳nobjs个,大小为size的区块
	static char *chunk_alloc(size_t size, int &nobjs);
 
	static char *start_free;     //内存池起始位置
	static char *end_free;       //内存池结束位置
	static size_t heap_size;     


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 data member的定义和初值设定
template 
char* __default_alloc_template::chunk_alloc(size_t size, int& nobjs);

template 
void* __default_alloc_template::refill(size_t n);

template 
void* __default_alloc_template::reallocate(void *p, size_t old_sz, size_t new_sz)

template 
__default_alloc_template::obj * __VOLATILE
__default_alloc_template ::free_list[__NFREELISTS] = 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

3.5.1 空间配置函数(allocate)


C++STL学习(13)STL深入(2) SGI STL空间配置器_第6张图片

static void * allocate(size_t n)
{
	obj * __VOLATILE * my_free_list;
	obj * __RESTRICT result;

	//如果n>128就直接调用第一级空间配置器
	if (n > (size_t) __MAX_BYTES) 
	{
		return(malloc_alloc::allocate(n));
	}

	//寻找到16个自由结点中的一个
	my_free_list = free_list + FREELIST_INDEX(n);

	result = *my_free_list;
	if (result == 0) 
	{
		//如果没有找到这样的free-list,就准备重新填充free-list
		void *r = refill(ROUND_UP(n));
		return r;
	}
	//如果有合适的的free-list,调整free-list然后返回结果
	*my_free_list = result -> free_list_link;
	return (result);
};

3.5.2 空间释放函数(deallocate)


C++STL学习(13)STL深入(2) SGI STL空间配置器_第7张图片

static void deallocate(void *p, size_t n)
{
	obj *q = (obj *)p;
	obj * __VOLATILE * my_free_list;   //这种定义的含义和作用?后面详述

	//大于128就调用第一级配置器
	if (n > (size_t) __MAX_BYTES) 
	{
		malloc_alloc::deallocate(p, n);   
		return;
	}
	
	//寻找对应的free-list
	my_free_list = free_list + FREELIST_INDEX(n);
	//调整free-list,回收区块
	q -> free_list_link = *my_free_list;
	*my_free_list = q;
}

obj * _VOLATILE * my_free_list;
    这个声明原来的形式是:obj** my_free_list,这样的话*my_free_list(空闲的内存块指针数组中的一个元素)可能被优化到寄存器中,从而使库代码无法lock住对它的读调用(如果在寄存器中则另一个线程可能会无意中修改该寄存器的值,而在内存中由于另一个线程没有访问权力所以不能修改)。要声明变量必须在内存中就要用volatile修饰,这里修饰的是*my_free_list,是free_list数组中的一个元素,而不是数组指针,所以volatile放在两个*中间。
(解释来自CSDN论坛)


3.5.3 重新填充函数(refill)

//返回一个大小为n的对象, 并且有时候会为适当的free list增加结点
//假设 n 已经适当上调到8的倍数
template 
void* __default_alloc_template::refill(size_t n)
{
    int nobjs = 20;
	//调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点
	//参数nobjs是pass by reference
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;
	//如果只获得一个区块,这个区块就分配给调用者用,free-list无新节点
    if (1 == nobjs) return(chunk);
	//否则准备调整free list,纳入新的结点
    my_free_list = free_list + FREELIST_INDEX(n);

    //在以下chunk空间内建立free list
    result = (obj *)chunk;
	//以下导引free list 指向新配置的空间(取得内存池)
    *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 -> free_list_link = 0;
            break;
        }
		else
		{
            current_obj -> free_list_link = next_obj;
        }
    }
    return(result);
}




你可能感兴趣的:(【C++,STL】,C++,STL循序渐进)