SGI STL 空间配置器

Posted Jqivin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SGI STL 空间配置器相关的知识,希望对你有一定的参考价值。


一、空间配置器

当我们使用stl标准容器的时候,我们就在用空间配置器。空间配置器总是隐藏在背后,默默的付出。但我们却没有感受的到,其实,在vector,list等标准容器在实现的时候,就指定了默认的空间配置器。
在这里插入图片描述

二、为什么使用空间配置器?

  当我们在使用new分配空间的时候,new有三种使用方法(new的三种用法:new的使用)。
  当我们使用new关键字构建一个对象的时候,无论我们申请多大的空间,都会有一些越界标志和头部信息,大大降低了空间的利用率,所以,我们如果能在申请大空间的时候,使用new关键字,申请小空间的时候使用内存池的空间直接分配。这样就大大减少了空间的浪费。(内存碎片)
在这里插入图片描述

  当我们使用关键字定义对象的时候,有两个步骤:调用::operator new申请空间,调动构造函数构建对象。当我们使用delete删除对象的时候,也有两个操作:调用析构函数析构对象,调用::operator deleter释放内存。

STL是怎么做的呢?(SGI的alloc的做法)
  STL进行了分离,第一个头文件只管理空间(内存,申请空间,释放空间)就相当于operator new;
第二个头文件只管理对象(构造和析构)。下图
在这里插入图片描述
第三个头文件是管理大块内存
在这里插入图片描述

三、construct.h

在这里插入图片描述

四、std::alloc的实现

关于std::alloc

std::alloc是SGI的一个特殊的空间配置器,SGI标准的空间配置器是std::allocator但是SGI却没有使用过它,因为这个标准的配置器只是简单的对operator new和operator delete做一个简单的封装,效率很低下。

stl_alloc.h 空间的配置与释放

介绍
  考虑到小型区块所可能造成的内存破碎问题,SGl设计了双层级配置器,一级配置器直接使用malloc ()和free() .第二级配置器则视情况采用不同的策略,如果要申请的内存大于128bytes,视之为“足够大”,调用一级配置器。否则,采用memory pool的方式进行内存的分配。
总之,
(1)一级配置器:malloc free
(2)二级配置器:采用不同的策略,空间大调用一级配置器,空间小调用内存池。
在这里插入图片描述

一级配置器

主要的接口有四个

1.
static void allocate(size_t n)
{	
	//若果有内存,即调用malloc成功;
	//如果没有内存,调用私有成员oom_malloc();
}
2.
static void reallocate(void* p,size_t new)
{
	//若果有内存即realloc(n)成功;
	//如果没有内存,调用私有成员oom_realloc();
}
3.
static void* deallocate(void* p,size_t new)
{
	free(p);
}
4.
set_alloc_handler()  //设置内存不足的处理函数

在这里插入图片描述

代码实现

namespace jqw
{
#if 0
#include<new>
#define _THROW_BAD_ALLOC throw std::bad_alloc;
#elif !defined(_THROW_BAD_ALLOC)
//#include<iostream>
#define _THROW_BAD_ALLOC std::cerr<<"out of memory" <<std::endl;exit(1)
#endif
	//非类型模板化,inst没有任何意义
	template <int inst>
	class _malloc_alloc_template
	{
	private:
		static void* oom_malloc(size_t n)
		{
			void (*my_malloc_handler)() = nullptr;
			void* result = nullptr;
			for (;;)
			{
				my_malloc_handler = __malloc_alloc_oom_handler;
				if (my_malloc_handler == nullptr)
				{
					_THROW_BAD_ALLOC;
				}
				(*my_malloc_handler)();
				result = malloc(n);
				//如果申请到了空间,就返回申请到的空间,否则再次进入循环,继续释放空间
				if (result != nullptr)
				{
					return result;
				}
			}
		}
		static void* oom_realloc(void* p, size_t new_sz)
		{
			void (*my_malloc_handler)() = nullptr;
			void* result = nullptr;
			for (;;)
			{
				my_malloc_handler = __malloc_alloc_oom_handler;
				if (my_malloc_handler == nullptr)
				{
					_THROW_BAD_ALLOC;
				}
				(*my_malloc_handler)();
				result = realloc(p, new_sz);
				//如果申请到了空间,就返回申请到的空间,否则再次进入循环,继续释放空间
				if (result != nullptr)
				{
					return result;
				}
			}
		}
		//返回值为void,无参的函数指针,在类外把它初始化为空 -- 静态成员初始化要在类外
		static void(*__malloc_alloc_oom_handler)();

	public:
		//申请空间,如果检测到有内存调用malloc,没有就调用oom_malloc
		static void* allocate(size_t n)
		{
			void* result = malloc(n);
			if (result == nullptr)
			{
				result = oom_malloc(n);
			}
			return result;
		}
		//释放空间,一级配置器直接使用free()
		static void* deallocate(void* p, size_t /* n */)
		{
			free(p);
		}
		//重新分配空间
		static void* reallocate(void* p, size_t /* old_sz */, size_t new_size)
		{
			void* result = realloc(p, new_size);
			if (result == nullptr)
			{
				result = oom_realloc(p, new_size);
			}
			return result;
		}
		typedef void(*malloc_handler)();    //定义一个函数指针

		//返回值是一个函数指针,参数也是一个函数指针
		//static void(*set_alloc_handler)(void(*f)()); //与下面的等价
		//在这个函数的作用是吧原来的__malloc_alloc_oom_handler返回(可能别的函数收到这个返回值就把这个函数指针的内容运行 -- 释放空间)
		//然后再把传过来的指针给给__malloc_alloc_oom_handler,供下次使用
		static malloc_handler set_alloc_handler(malloc_handler f)
		{
			malloc_handler 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;

	typedef _malloc_alloc_template<0> malloc_alloc;
}

一级配置器的测试:首先我们定义两个全局指针变量用来处理内存不足的情况,也就是储备内存空间。

//一级配置器的测试代码
char* p1 = (char*)malloc(1024 * 1024 * 200);
char* p2 = (char*)malloc(1024 * 1024 * 100);

void jqwfun2()
{
	free(p2);
	p2 = nullptr;
	jqw::malloc_alloc::set_alloc_handler(nullptr);
}
void jqwfun1()
{
	free(p1);
	p1 = nullptr;
	jqw::malloc_alloc::set_alloc_handler(jqwfun2);
}
int main()
{
	jqw::malloc_alloc::set_alloc_handler(jqwfun1);
	int* p = (int*)jqw::malloc_alloc::allocate(sizeof(int));
	*p = 100;
	free(p);
	return 0;
}

如果把allocate中的result置为空,执行到oom_malloc的时候,my_malloc_handler就变成了jqwfun1.
在这里插入图片描述
注意
SGI第一级配置器的allocate ()和reallocate ()都是在调用malloc ()和realloc ()不成功后,改调用oom_malloc()和oom_realloc ()。后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务。但如果"内存不足处理例程"并未被客端设定,oom_malloc()和oom_realloc ()便老实不客气地调用_THROW_BAD_ALLOC,丢出 bad_alloC异常信息,或利用exit(1)硬生生中止程序。

二级配置器

参考文章:std::alloc二级配置器

接口

整个计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于_USE_MALLOc6是否被定义(唔,我们可以轻易测试出来,SGI STL并未定义_USE_MALLOC) :
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

书籍:《STL源码剖析 - 侯捷》

以上是关于SGI STL 空间配置器的主要内容,如果未能解决你的问题,请参考以下文章

STL 之 空间配置器(allocator)

STL 之 空间配置器(allocator)

SGI STL内存配置器存在内存泄漏吗?

SGI STL内存配置器:内存泄漏?

C++ STL重点难点复习总结

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器