Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >走进STL - 空间配置器,STL背后的故事

走进STL - 空间配置器,STL背后的故事

作者头像
看、未来
发布于 2020-08-26 03:07:49
发布于 2020-08-26 03:07:49
2.2K00
代码可运行
举报
运行总次数:0
代码可运行

1、何为“空间配置器”

a、为何需要先了解空间配置器

从使用STL层面而言,空间配置器并不需要介绍,所以我的“走近STL”系列中并没有它的位置。

但若是从STL实现角度出发,空间配置器确实首要理解的。

作为STL设计背后的故事,空间配置器总是在默默地付出着。为什么你可以使用算法处理数据,为什么可以对容器进行操作,为什么迭代器可以遍历空间,这一切的一切,都有“空间配置器”的功劳。

而如果不经过本章,看后续章节会给自己徒增许多烦恼。

b、SGI STL专属空间配置器

SGI STL 的空间配置器与众不同,且与STL标准规范不同,其名为alloc,而非allocator。

SGI STL allocator未能符合标准,不过这并不会给我们造成困扰,因为我们没什么事儿不会自己去配置这个。而SGI的每一个容器都已经预设好了alloc。举例声明如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template <class T,class Alloc = alloc>	//预设使用alloc配置器
class vector{···};

虽然SGI也配置了allocatalor,但是它自己并不使用,也不建议我们使用,原因是效率比较感人,

因为它只是在基层进行配置/释放空间而已。

c、alloc的优势

三言两语无法说清,也不具说服力,具体请看下面 alloc全貌 的介绍。

(友情提醒:可以参考 2.c - c.1)

2、alloc全貌

a、 C++内存配置操作与释放操作

且看以下小栗子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class test{···};

test *pf = new test;	//配置内存,然后构造对象
···
delete pf;		//析构对象,然后释放内存

这其中的new包含两个步骤:调用 ::operator new分配内存 ,调用test::test() 创建对象

其中的delete也包含两个步骤:调用test::~test()析构对象,调用 ::operator delete 释放空间

为了精密分工,STL allocator 将这两个步骤分开来,由alloc:allocate()负责内存配置操作,内存释放操作由alloc:deallocate()负责;对象构造操作由 ::construct()负责,对象析构操作由 ::destroy() 负责。

STL标准规则告诉我们,配置器定义于之中,SGI的内含以下两个文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stl_alloc.h>		//负责内存空间的配置与释放
#include <stl_construct.h>	//负责对象内容的构造与析构

来看张图:

初学作图,有点丑,还能看。

b、析构和构造的基本函数

construct()和destroy的源代码,睁大眼睛哦,虽然这两个函数不难

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//以下是construct()函数

#include <new.h>

template <typename T1,typename t2>
inline void construct(T1 *p,const T2& value)
{
	new (p) T1(value);	//调用T1::T1(value)
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//以下是destroy()函数第一版本

template <typename T>	//第一版本,接受一个指针

inline void destroy(T *pointer)
{
	pointer->~T();	  //调用 dtor ~T()
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//以下是destroy()函数第二版本,接受两个迭代器

template <typename ForwardIterator>

inline void destory(ForwardIterator first,ForwardIterator last)
{
	__destory(first,last,value_type(first));
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//判断元素的数值型别(value type)是否无关痛痒(trivial destructor)
template <typename ForwardIterator,typename T>

inline void __destroy(ForwardIterator first,ForwardIterator last,T*)
{
	typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;//注1
	__destroy_aux(first,last,trivial_destructor());
}

注1:这一句如果看不懂,我已经备好了排忧解难工具包

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//如果是有关痛痒(non-trivial destructor)的

template <typename ForwardIterator,typename T>

inline void __destroy_aux(ForwardIterator first,ForwardIterator last,__false_type)
{
	for(;first<last;++first)
	{
		destroy(&*first);
	}
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//如果是trivial destructor,则no-op

template <typename ForwardIterator,typename T>

inline void __destroy_aux(ForwardIterator first,ForwardIterator last,__true_type){}

这么长一串怕是看呆了,再来张图吧,献丑了

c、空间的配置与释放(alloc)

上面那一块是对象的构造和析构,接下来要讲的是对象构造和析构背后的故事——(内存的分配与释放),不要搞混了哦。

c.1 真·alloc设计奥义

对象构造和析构之后的内存管理诸项事宜,由<stl_alloc.h>一律负责。

SGI对此的设计原则如下:

  • 向system heap索要空间
  • 考虑多线程状态
  • 考虑内存不足时的应变措施
  • 考虑过多小型区块造成的内存碎片问题

考虑到小型区块可能造成的内存破碎问题,SGI为此设计了双层级配置器。

当配置区块超过128bytes时,称为足够大,使用第一级配置器,直接使用malloc()和free()。

当配置区块不大于128bytes时,为了降低额外负担,直接使用第二级配置器,采用复杂的memory pool处理方式。

无论使用第一级配接器或是第二级配接器,alloc都为其包装了接口,使其能够符合STL标准。

c.2 alloc一级配置器源码(截取)

睁大眼睛吧,看看这鬼斧生工的设计:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <new>

//注意,alloc不接受“template 型别参数”,所以就算你定义了也用不上

class __malloc_alloc_template
{
	private:	//这里面都是函数指针,用来处理内存不足的情况
	static void *oom_malloc(size_t);	
	static void *oom_realloc(void *,size_t);
	static void (* __malloc_alloc_oom_handler)();

	public:
	
	//正常空间配置
	static void * allocate(size_t n)
	{
		void *result = malloc(n);	
		if( 0 == result)
			result = oom_malloc(n);
		return result;
	}

	//正常空间回收
	static void deallocate(void *p,size_t /*n*/)
	{
		free(p);
	}

	//正常重分配空间
	static void * reallocate(void *p,size_t /* old_sz */,size_t new_sz)
	{
		void * result = realloc(p,new_sz);
		if( 0 == result )
			result = oom_realloc(p,new_sz);
		return result;
	}
}

//截取片段,留作向后拓展的部分这里就不提了

//下面为异常情况处理(空间不够)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void *__malloc_alloc_tempate<0>::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);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void *__malloc_alloc_tempate<0>::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);
}

SGI 以malloc,而非 ::operator new 来分配内存,有历史因素,也有重配内存的因素在里面吧。

c.3 alloc 二级配置器源码(截取)

如果累了,建议先歇会儿,保护眼睛保护大脑。

接下来的这部分,将会更加的让我们为大师的智慧折服。

第二级配置器多了一些机制,专门针对内存碎片。内存碎片化带来的不仅仅是回收时的困难,配置也是一个负担。额外负担永远无法避免,毕竟系统要划出这么多的资源来管理另外的资源,但是区块越小,额外负担率就越高。

(索求任何一块内存,都得有一些“回扣”要交给系统)

SGI第二级配置器解决了多少问题呢?那就看各位的理解到什么程度了。

SGI第二级配置器的做法是“:sub-allocation (层次架构):

  • 每次分配一大块内存,并维护对应之自由链表(free-list)。
  • 下次若再有相同大小的内存需求,则直接从free-lists中取出。
  • 如果客端释放小额区块,就收回free-lists.

苍白的文字啊,看图:

清楚明了吧。

为了方便管理,SGI配置器会主动将任何小区块的内存需求量提升至8的倍数,就是整数字节的大小。

如果客端要求2个比特,就会自动分配到8比特。

并维护了16个free-list,图中已明确指出了。

如果我的图太难看,我在别的地方也找了一张:

free-list的节点结构真的是要惊叹的了:

看:

注:如果关于联合体把你困住了,我帮你解决了:工具包

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
union obj
{
	union obj *free_list_link;
	char client_data[1];
}

乍一看可能平淡无奇,认真一看,那个 union obj共出现了两次。这样有什么好处?

由于union的缘故,从其第一字段来看,obj可被视为一个指针,执行指向另一个obj;

从第二个字段来看,obj可以被看作一个指针,指向实际区块。

这样的好处在于,不会为了维护链表所必须的指针而造成另一次内存浪费。

秀吧,天秀!

好,接下来是第二级配置器的部分实现内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum {__ALIGN = 8};	//小型区块的上调边界
enum {__MAX_BYTES = 128};	//小型区块的上限
enum {__NFREELISTS/ALIGN}	//free-list个数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class __default_alloc_template{
	
private:
//将byte上调至8的倍数
	static size_t ROUND_UP(size_t bytes){
		return (((bytes)+__ALIGN-1) &~ (__ALIGN -1));	
	}
private:
	//free-list的节点构造
private:
	//16个free-list
	static obj *volatile free_list[__NFREELISTS];
	//根据区块大小,决定使用n号free-list。n从1开始算
	static size_t FREELIST_INDEX(size_t bytes)	{
		return return (((bytes)+__ALIGN-1) / __ALIGN -1);	
	}
}

···
c.4空间配置函数allocate

文字叙述前面已经很详尽了,倒是代码零零散散,这里将代码串起来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void *alloc(size_t n)
{
	obj * volatile my_free_list;
	obj * result;

//一级
	if( n > (size_t) __MAX_BYTES)
	{
		return (malloc_alloc::allocate(n));
	}

	my_free_list = free_list + FREELIST_INDEX(n);
	result = *my_free_list;
	if(result == 0)
	{
		//没有可用free-list,准备重新填充
		void *r = refill(ROUND_UP(n));
		return r;
	}
	*my_free_list = result->free_list_link;
	return (result);
}
c.5 空间释放函数deallocate

有配置自然要有相应的释放函数了。

文字叙述已经很详尽了,直接看代码吧。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void deallocate(void *p,size_t n)
{
	obj *q = (obj *)p;
	obj *volatile *my_free_list;

	if( n > (size_t) __MAX_BYTES)
	{
		malloc_alloc::deallocate(p,n);
		return n;
	}

//寻找对应的free list
	my_free_list = free_list + FREELIST_INDEX(n);

//调整free_list,回收区块
	q->free_list_link = *my_free_list;
	*my_free_list = q;
}
c.6 重新填充free lists 函数refill

前面将这块放空。始终觉得看不过去。

当分配空间时发现没有可用区块空间了,就需要使用refill从内存池中申请空间。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void * __default_alloc_template</**参数不管它*/>::refill(size_t n)
{
	int nobjs = 20;

//调用chunk_alloc(),尝试取得nobjs个区块作为新节点
	char *chunk = chunk_alloc(n,nobjs);
	obj *volatile *my_free_list;
	obj *result;
	obj *current_obj,*next_obj;
	int i;

	if(1 == nobjs)	//要是就剩一块儿了,先给调用者
		return (chunk);
	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++)	//从1开始,0给别人了
	{
		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. 内存池的chunk_alloc()操作

从内存池中取空间给free list 使用,是chunk_alloc() 的工作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
char * __default_alloc_template</*参数不管它*/>::chunk_alloc(size_t size,int &nobjs)
{
	char * result;
	size_t total_bytes = size *nobjs;
	size_t bytes_left = end_free - start_free;	//内存池还有多少水

	if(bytes_left>=total_bytes)
	{
		result = start_free;
		start_free += total_bytes;
		return (result);
	}
	else if(bytes_left >= size)	//不够总量,但是总归还是有一些存货的
	{
		nobjs = bytes_left/size;
		total_bytes = size*nobjs;
		result = start_free;
		start_free += total_bytes;
		return (result);
		//能给多少多少给多少吧
	}
	else
	{
		//一点都没了
		size_t byte_to_get = 2*total_bytes + ROUND_UP(heap_size >> 4);
		//先找些零零散散的拼一下看看
		if(bytes_left>0)
		{
			obj * volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);
			//调整free-list,将残余空间塞进去
			((obj *)start_free)->free_list_link = *my_free_list;
			*my_free_list = (obj *)start_free;
		}

	//记得我们开头说过,空间总是从system-heap中索求的,现在是时候去索求了
	//这块省略吧,太长了
	}
}

万一山穷水尽,连system heap都没空间了,那就一首凉凉。

篇章说明:

本篇主要来自与侯捷先生的《STL源码剖析》。

我在CSDN上也看到不少此类文章,但是大多是生搬硬套,很多点都讲的不清不楚,我在查资料的时候也是很懵。

所以我尽量把自己能看懂的内容摘取,看不懂的都去查了资料,工具包都嵌在文中了,或注释,或链接。

下一篇章将会是《迭代器》,不过不知道要写多久,这篇整整写了五天。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/05/08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】STL之空间配置器
前面在模拟实现 vector 、 list 、 map 、 unordered_map 等容器时,所有需要空间的地方都是通过
小文要打代码
2025/05/17
560
【C++】STL之空间配置器
【C++航海王:追寻罗杰的编程之路】关于空间配置器你知道多少?
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的,在默默地工作。虽然在常规使用STL时,可能用不到它,但站在学习研究的角度,学习它的实现原理对我们有很大的帮助。
枫叶丹
2024/08/06
990
【C++航海王:追寻罗杰的编程之路】关于空间配置器你知道多少?
C++ STL空间配置源码分析以及实现一
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/details/80560066
bear_fish
2018/09/14
9370
C++ STL空间配置源码分析以及实现一
STL之空间配置器
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的,在默默地工作。
海盗船长
2020/08/27
4680
【c++】 C语言的输入与输出&&C++的IO流&&STL空间配置器
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位可以是bit,byte,packet )的抽象描述
用户10925563
2024/08/29
1540
【c++】 C语言的输入与输出&&C++的IO流&&STL空间配置器
SGI STL二级空间配置器--内存池源码剖析
通过SGI STL vector底层源码逐步分析内存池。 事实上,在我们使用STL容器时,有一点没有关心到的是我们默认使用了标准库里边的空间配置器,当然标准这样的做法是为了减少学习成本,但是当我们深入学习时,就一定要明白这些容器底层是如何工作,才能注重效率,才能用好STL容器。正如侯捷先生所说"源码之后,了无秘密。", 下面通过vector容器先看一级空间配置器:
lexingsen
2022/02/25
5890
SGI STL二级空间配置器--内存池源码剖析
如何用Cpp实现一个内存池
当需要频繁的进行new和delete操作时,可能会导致产生很多内存碎片。 所以用一个内存池来对这些空间进行管理,可以有效地提高内存利用率。 另外也可以用内存池和placement new来一块使用。 在STL中也有一个内存池的实现,还是非常巧妙的。在此学习并模仿着写一个。
yifei_
2022/11/14
4560
std源码剖析及C++内存管理(二)
在第一节中提到,malloc的内存块布局如上,其中cookie(记录区块大小)小,浪费率高,因为cookie始终占8字节。cookie是我们不需要的,如果大量调用malloc的话cookie总和会增多,这会造成较大的浪费,因此减少malloc调用次数,去掉cookie总是好的。
公众号guangcity
2019/09/20
1.7K0
std源码剖析及C++内存管理(二)
用C来实现内存池
介绍:        设计内存池的目标是为了保证服务器长时间高效的运行,通过对申请空间小而申请频繁的对象进行有效管理,减少内存碎片的产生,合理分配管理用户内存,从而减少系统中出现有效空间足够,而无法分配大块连续内存的情况。 目标:     此次设计内存池的基本目标,需要满足线程安全性(多线程),适量的内存泄露越界检查,运行效率不太低于malloc/free方式,实现对4-128字节范围内的内存空间申请的内存池管理(非单一固定大小对象管理的内存池)。 内存池技术设计与实现     本内存池的设计方法主要参考S
猿人谷
2018/01/17
3.1K1
用C来实现内存池
C++空间配置器
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的配置器,在默默地工作。下图是空间配置器、malloc的关系图:
二肥是只大懒蓝猫
2023/03/30
3510
C++空间配置器
从vector扩容看STL空间分配器的本质
熟悉STL的同学始终都绕不过的一个地方,尤其是面试时也会被问及容器的知识点:vector。
CPP开发前沿
2021/11/25
1.1K0
C++ STL空间配置源码分析以及实现二
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/details/80573204
bear_fish
2018/09/14
6740
C++ STL空间配置源码分析以及实现二
STL list源码分析以及实现
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/details/80643481
bear_fish
2018/09/14
1.6K0
STL list源码分析以及实现
《逆袭进大厂》第四弹之C++重头戏STL30问30答
这是《逆袭进大厂》系列的第四期,本期是 C++ 重头戏,也就是标准模板库 STL 的内容,本期是 24098 个字。
拓跋阿秀
2021/03/21
1.6K0
走进STL - 序列式容器(常用篇)
在序列式容器的大家庭里,比较常用的还是vector和list。 本篇就重点讲这两个容器的实现。
看、未来
2020/08/26
4980
C++STL-vector实现 空间配置器
通过观察打印结果,得到一下几个存在的问题? 1.vector中什么元素都没有,居然就进行了10次构造?按道理,没有push_back进去元素,我们只需要申请初始空间即可,不需要进行构造。 2.pop_back推出vector尾部的元素时,没有进行析构,如果此时vector的元素为对象并且持有堆资源,那么就会造成内存的泄露? 3.pop_back推出尾部元素时,只需要析构该位置的元素即可,不需要释放空间?空间的释放时机是vector对象生命周期结束时 造成上述结果的缘由: 1.vector的构造函数直接使用了new,包含两个动作,开辟空间和调用构造函数进行构造。 2.pop_back时,直接 --_last,没有进行该位置对象的析构。但是如果单纯的使用delete,不仅不会调用析构函数析构该位置的对象,还会删除该位置的内存。 综上:本质的问题就是new没有将开辟内存和构造对象这两个操作步骤分离开来。 delete没有将析构对象和释放内存这两个操作分离开来。
lexingsen
2022/02/25
2650
C++STL-vector实现 空间配置器
C++空间配置器
空间配置器的作用: (1)将对象构造和内存开辟进行分离。 (2)将对象析构和内存释放进行分离。
lexingsen
2022/02/25
2870
【redis6.0.6】redis源码慢慢学,慢慢看 -- 第二天:空间配置(zmalloc)
为了大家看文中那一堆的“#”不至于晕掉,建议先看一下这篇:讲通C/C++预编译/条件编译指令 #ifdef,#ifndef,#endif,#define,…
看、未来
2020/08/31
7140
STL小结
STL就是Standard Template Library,标准模板库。这可能是一个历史上最令人兴奋的工具的最无聊的术语。从根本上说,STL是一些“容器”的集合,这些“容器”有list, vector,set,map等,STL也是算法和其它一些组件的集合。这里的“容器”和算法的集合指的是世界上很多聪明人很多年的杰作。是C++标准库的一个重要组成部分,它由Stepanov and Lee等人最先开发,它是与C++几乎同时开始开发的;一开始STL选择了Ada作为实现语言,但Ada有点不争气,最后他们选择了C++,C++中已经有了模板。STL又被添加进了C++库。1996年,惠普公司又免费公开了STL,为STL的推广做了很大的贡献。STL提供了类型安全、高效而易用特性的STL无疑是最值得C++程序员骄傲的部分。每一个C++程序员都应该好好学习STL。大体上包括container(容器)、algorithm(算法)和iterator(迭代器),容器和算法通过迭代器可以进行无缝连接。
Twcat_tree
2022/11/30
9350
STL小结
从uClibc部分源码总结固件利用思路的变化
审计固件的时候碰到了一个mips64下uClibc堆管理利用的问题,恰巧网络上关于这个的分析不是很多,于是研究了一下。并不是很全面,做个索引,若有进一步了解时继续补全。
赤道企鹅
2022/08/01
7450
推荐阅读
相关推荐
【C++】STL之空间配置器
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验