STL源码剖析-Allocator

2023-10-26

一、第一级配置器

顾名思义,Allocator是用来做内存分配的。

第一级配置器指的是当需要128bytes以上的内存时直接使用malloc和realloc进行内存分配,代码如下:

	/*
	** @ 第一级配置器
	** @ 2023/04/07
	*/
	template<int __inst>
	class __malloc_alloc_template {
	private:
		static void* _S_oom_malloc(size_t);
		static void* _S_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 = _S_oom_malloc(__n);
			return __result;
		}

		static void deallocate(void* __p) {
			free(__p);
		}

		static void* reallocate(void* __p, size_t __new_sz) {
			void* __result = realloc(__p, __new_sz);
			if (0 == __result)
				__result = _S_oom_realloc(__p, __new_sz);
			return __result;
		}

		// 注册并返回旧的异常处理函数
		static void(*__set_malloc_handler(void(*__f)()))() {
			void(*__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;

	template <int __inst>
	void*
		__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n) {
		void* __result;
		for (;;)
		{
			if (__malloc_alloc_oom_handler != nullptr)
				__malloc_alloc_oom_handler();
			__result = malloc(__n);
			if (__result)
				return __result;
		}
	}

	template <int __inst>
	void*
		__malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n) {
		void* __result;
		for (;;)
		{
			if (__malloc_alloc_oom_handler != nullptr)
				__malloc_alloc_oom_handler();
			__result = realloc(__p, __n);
			if (__result)
				return __result;
		}
	}

	typedef __malloc_alloc_template<0> malloc_alloc;

1、这里模板的定义为template<int __inst>是什么意思呢?既然指定了int类型为什么还要用模板呢?

__malloc_alloc_template中给出的是静态函数接口,所以callback/handler也得是静态的形式,但是我想让不同的Allocator拥有不同的callback/handler,那就只有定义多个Allocator类了(Allocator1,Allocator2,Allocator3......)。有没有办法可以解决这个问题?

template<int __inst>就可以解决上面的问题。我们用不同的参数显式实例化__first_stage_alloc,拿到的静态成员地址是不同的,所以我们就可以给他们注册不同的callback/handler函数。

2、__set_malloc_handler这是返回值为函数指针的函数~可以学习一下。

3、malloc(0) 会出现什么情况?是可以返回一个有效指针的,内存的大小是由机器决定的,具体可以去查阅更多资料。

二、第二级配置器

原先我以为这里的内存池是类似ringbuffer一样的东西,所以看这段代码时觉得很迷。其实维护的是这样一个东西:

bufferpool分为两个部分,一个部分是用_S_free_list维护的链表,另一部分是还未分配到链表的buffer。

buffer(小于等于128bytes)分配的步骤如下:

a.先从链表中获取buffer chunk

b.如果链表中没有了,从pool中获取chunk加入到链表中

c.如果pool中没有了,那么重新分配buffer到链表中,再返回给调用者

buffer(小于等于128bytes)销毁的步骤:找到对应链表,插入到链表头部

	template <int __inst>
	class __default_alloc_template {
	private:
		enum { _ALIGN = 8 };
		enum { _MAX_BYTES = 128 };
		enum { _NFREELISTS = _MAX_BYTES/_ALIGN };
		// 8 bytes align
		static size_t _S_round_up(size_t __bytes) {
			return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
		}

		union _Obj {
			union _Obj* _M_free_list_link;
			char _M_client_data[1];
		};

		// free list数组
		static _Obj* volatile _S_free_list[_NFREELISTS];

		// 根据区块大小决定使用第几号区块(0-15)
		static  size_t _S_freelist_index(size_t __bytes) {
			return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
		}
		// 
		static void* _S_refill(size_t __n);
		// 
		static char* _S_chunk_alloc(size_t __size, int& __nobjs);
		// 内存池起始位置
		static char* _S_start_free;	
		// 内存池结束位置
		static char* _S_end_free;		
		static size_t _S_heap_size;

	public:
		static void* allocate(size_t __n) {
			void* __ret = 0;
			if (__n > (size_t)_MAX_BYTES) {
				__ret = malloc_alloc::allocate(__n);
			}
			else
			{
				// 找到合适大小的链表
				_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__n);
				_Obj* __result = *__my_free_list;
				if (__result == 0)
				{
					__ret = _S_refill(_S_round_up(__n));
				}
				else {
					*__my_free_list = __result->_M_free_list_link;
					__ret = __result;
				}
			}
			return __ret;
		}

		static void deallocate(void* __p, size_t __n) {
			if (__n > (size_t)_MAX_BYTES)
				malloc_alloc::deallocate(__p, __n);
			else
			{
				_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__n);
				_Obj* __q = (_Obj*)__p;
				__q->_M_free_list_link = *__my_free_list;
				*__my_free_list = __q;
			}
		}

		static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);
	};

	template <int __inst>
	char* __default_alloc_template<__inst>::_S_start_free = 0;
	template <int __inst>
	char* __default_alloc_template<__inst>::_S_end_free = 0;
	template <int __inst>
	size_t __default_alloc_template<__inst>::_S_heap_size = 0;
	// 使用模板类对象,需要在类名前面使用typename
	template <int __inst>
	typename __default_alloc_template<__inst>::_Obj* volatile
		__default_alloc_template<__inst>::_S_free_list[
			__default_alloc_template<__inst>::_NFREELISTS
		] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

	template<int __inst>
	char* __default_alloc_template<__inst>::_S_chunk_alloc(size_t __size, int& __nobjs)
	{
		char* __result;
		size_t __total_bytes = __size * __nobjs;
		size_t __bytes_left = _S_end_free - _S_start_free;
		if (__bytes_left >= __total_bytes) {	/* 如果剩余空间总空间 > n x size,将剩余buffer加入到链表中,最多加20块*/
			__result = _S_start_free;
			_S_start_free += __total_bytes;
			return(__result);
		}
		else if (__bytes_left >= __size) {		/* n x size > 剩余空间总空间 > size, 将剩余buffer加入到链表中 */
			__nobjs = (int)(__bytes_left / __size);
			__total_bytes = __size * __nobjs;
			__result = _S_start_free;
			_S_start_free += __total_bytes;
			return(__result);
		}
		else { // 剩余空间总空间 < size
			// 分配2倍的total bytes buffer给buffer pool
			size_t __bytes_to_get =	2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
			// 如果pool中还有剩余buffer,将剩余buffer加入到对应的链表中
			if (__bytes_left > 0) {
				_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left);

				((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;
				*__my_free_list = (_Obj*)_S_start_free;
			}
			// 分配buffer
			_S_start_free = (char*)malloc(__bytes_to_get);	
			if (0 == _S_start_free) {
				// 如果分配失败,则从其他链表中先获取一块buffer
				size_t __i;
				_Obj* volatile* __my_free_list;
				_Obj* __p;
				for (__i = __size; __i <= (size_t)_MAX_BYTES; __i += (size_t)_ALIGN) {
					__my_free_list = _S_free_list + _S_freelist_index(__i);
					__p = *__my_free_list;
					if (0 != __p) {
						*__my_free_list = __p->_M_free_list_link;
						_S_start_free = (char*)__p;
						_S_end_free = _S_start_free + __i;
						return(_S_chunk_alloc(__size, __nobjs));
					}
				}
				// 如果其他链表中也没有则调用第一级分配器中的allocate,这里有异常处理
				_S_end_free = 0;
				_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
			}
			_S_heap_size += __bytes_to_get;
			_S_end_free = _S_start_free + __bytes_to_get;
			// 将pool中的buffer加入到链表中
			return(_S_chunk_alloc(__size, __nobjs));
		}
	}

	template<int __inst>
	void* __default_alloc_template<__inst>::_S_refill(size_t __n)
	{
		// 每个链表最多维护20块buffer
		int __nobjs = 20;
		char* __chunk = _S_chunk_alloc(__n, __nobjs);
		_Obj* volatile* __my_free_list;
		_Obj* __result;
		_Obj* __current_obj;
		_Obj* __next_obj;
		int __i;

		if (1 == __nobjs) 
			return(__chunk);
		__my_free_list = _S_free_list + _S_freelist_index(__n);

		__result = (_Obj*)__chunk;
		// 构建链表
		*__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->_M_free_list_link = 0;
				break;
			}
			else {
				__current_obj->_M_free_list_link = __next_obj;
			}
		}
		return __result;
	}

	template <int __inst>
	void* __default_alloc_template<__inst>::reallocate(void* __p, size_t __old_sz, size_t __new_sz)
	{
		void* __result;
		size_t __copy_sz;

		if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {
			return(realloc(__p, __new_sz));
		}
		if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
		__result = allocate(__new_sz);
		__copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;
		memcpy(__result, __p, __copy_sz);
		deallocate(__p, __old_sz);
		return(__result);
	}

    typedef __default_alloc_template<0> alloc;

1、这里的8 bytes align很是巧妙:将原先的数字加7进位,然后用与运算减去余数。

2、使用union维护链表来节省内存

书里和网上查到的资料对此特性的描述:"用联合体union来维护链表,从其第一个字段观之可以视为一个指针,指向下一个区块;从其第二个字段观之是存有本块内存首地址"。一开始我不能理解这句话,后来我发现联合体中成员的地址等于联合体的地址:

	union _Obj {
		union _Obj* _M_free_list_link;
		char _M_client_data[1];
	};

	int v = 10;
	_Obj *o = (_Obj*)&v;
	printf("%x\n", o->_M_client_data);			// 0xb8fb2c
	printf("%x\n", &(o->_M_free_list_link));	// 0xb8fb2c

数组本身就是一个地址,打印数组就是打印的内存块的首地址,而这个数组具体有几个元素并不重要。

指针本身也是一个变量,有自己地址,成员指针的地址等同于union的地址,给成员指针赋值等同于给union存下一块buffer的地址。

3、用typename来表示这是一个类型

	template <int __inst>
	typename __default_alloc_template<__inst>::_Obj* volatile
		__default_alloc_template<__inst>::_S_free_list[
			__default_alloc_template<__inst>::_NFREELISTS
		] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

模板显式实例化: 

template typename TinySTL::__default_alloc_template<0>;

4、看到这里我还有几个问题?

deallocate并没有真正释放小于128bytes的buffer,随着buffer分配的越来越多,这里的处理是不是欠妥?

分配出来的buffer是一整块buffer中的一部分,写的时候不对size进行限制,指针越界怎么处理呢?

后续看到了再来解答.

三、标准空间配置器

上面是SGI的高效率配置器,除了这个,SGI还有一个标准的配置器,以下是精简的代码:

namespace TinySTL {
	typedef int	ptrdiff_t;

	template<typename T>
	inline T* allocate(ptrdiff_t size, T*) {
		T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
		if (tmp == 0)
		{
			printf("out of memory\n");
			exit(1);
		}
		return tmp;
	}

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

	template<typename 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;
		
		pointer allocate(size_type n) {
			return TinySTL::allocate((difference_type)n, (pointer)0);
		}

		void deallocate(pointer p) {
			TinySTL::deallocate(p);
		}
	};
}

可以看到,allocate和deallocate分别调用的是::operator new和::operator delete,这是我第一次见到这种用法,网上查找资料可以知道,他们实际上是两个运算符,是调用的malloc和free在堆上申请、释放空间。new一个对象分为三个步骤,调用::operator new分配空间,构造对象和返回指针,一般来说我们不会去重载new运算符,用的是全局的。其他的相关内容可以参考new详解

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STL源码剖析-Allocator 的相关文章

  • 在 Visual Studio 中为“将 JSON 粘贴为类”配置类和成员名称大小写(lowerCamelCase 与 UpperCamelCase)

    考虑这个 JSON firstName John lastName Doe homeAddress streetAddress 123 Main St city Boston state MA postalCode 02110 employ
  • C# 中集合作为装饰器

    在设计集合基础设施时 我们遇到了一个非常 明显 的问题 假设您需要实现许多 子 类型的集合 其中一个方面是存储相关 list array等等 而另一个是行为相关 ordered 仅删除 可观察到的 每次更改时都会触发一个事件 等 显然 再次
  • 如何使用 Windows 粘贴命令将文本粘贴到 C# 中的其他应用程序?

    如何调用互操作来使用 Windows Pastse 命令将文本粘贴到 C 中的其他应用程序 调用互操作 我的意思是如何对 C 进行编程相同的右键单击粘贴文本 在某些情况下这可能有点棘手 但实际上非常简单且容易做到 下面的示例介绍了如何使用文
  • QT 5.6 QWebEngine不保存cookie

    我正在创建名为 webengine 的简单 QT 应用程序 pWebView new QWebEngineView this pWebView gt load QUrl http technoz ru pWebView gt show On
  • jni.h:没有这样的文件或目录

    我一直在关注本教程 http www java tips org other api tips jni simple example of using the java native interface html 在第 5 步 我从 GCC
  • 在 MVC 5 中,如何在单个 Ajax POST 请求中发送 ViewModel 和文件?

    我有一个 ASP NET MVC 5 应用程序 我正在尝试发送带有模型数据的 POST 请求 并且还包括用户选择的文件 这是我的 ViewModel 为了清晰起见进行了简化 public class Model public string
  • 如何在 CUDA 中执行多个矩阵乘法?

    我有一个方阵数组int M 10 以便M i 定位第一个元素i th 矩阵 我想将所有矩阵相乘M i 通过另一个矩阵N 这样我就收到了方阵数组int P 10 作为输出 我看到有不同的可能性 分配不同元素的计算M i 到不同的线程 例如 我
  • XML 字符串到 XML 文档

    我有一个完整的 XML 文档String我需要将其转换为 XML 文档并解析文档中的标签 此代码示例取自csharp examples net http www csharp examples net xml nodes by name 写
  • 如何序列化其类相互引用的类层次结构,但避免 XmlInclude?

    我有一个类的层次结构 我想使用XmlSerializer类及其相关属性 有一个基本抽象类 然后是相当多的派生类 在下面的代码中 我已将派生类的数量减少到五个 但实际代码中还有更多 这些类形成一个层次结构 并且经常包含对层次结构中类的实例的引
  • 如何在 C# winforms 中翻译文本

    我需要翻译一些文本 我正在尝试使用谷歌翻译器来翻译它 我检查了这个article http martinnormark com translate text in c using google translate 但我在以下代码中遇到异常
  • 为什么无法在 android 中包含 iostream?

    已安装 android ndk r7 并尝试编译 cpp 文件 include
  • 如何在 servicestack.net 中实现身份验证

    我正在调查 servicestack net 但它的示例和文章似乎没有涵盖身份验证 这是由 servicestack net 处理的东西 如果是的话如何处理 我特别有兴趣实现对以下方面的支持 OAuth 因此能够检查原始请求并验证它 检索关
  • 未初始化的枚举变量值

    我使用 enum 声明新类型 DAY 然后从中声明两个变量 day1 和 day2 然后当我使用未初始化的值时 我应该看到 0 到 6 之间的值 因为 enumlist 中的值介于 0 到 6 之间 但我收到了这些值改为 858993460
  • 推导具有两个以上参数的 std::function

    我想知道为什么std function http en cppreference com w cpp utility functional function只知道有两个参数的函数 我已经编写了一些运行良好的代码 但存在许多限制 欢迎任何反馈
  • C++ 抛硬币程序错误

    我正在尝试计算抛硬币中连续的正面朝上的次数 不幸的是 我的连续头计数器没有正确增加 有任何想法吗 代码和示例输出如下 include
  • __syncthreads() 死锁

    如果只有部分线程执行 syncthreads 会导致死锁吗 我有一个这样的内核 global void Kernel int N int a if threadIdx x
  • 哪个对缓存最友好?

    我试图很好地掌握面向数据的设计以及如何在考虑缓存的情况下进行最佳编程 基本上有两种情况我无法完全确定哪个更好以及为什么 是拥有一个对象向量更好 还是拥有对象原子数据的多个向量更好 A 对象向量示例 struct A GLsizei mInd
  • C 结构体中的 Typedef

    首先是令我困惑的代码 typedef struct Object typedef int MyInt void destructor Object void constructor struct Object Object 为什么编译器阻止
  • 静态、非成员或静态非成员函数?

    每当我有一些 实用 方向的功能时 我最终都会想知道哪个选项是最好的 例如 在我正在工作的上下文中打印消息结构 自己的或外部的 一些编码 解码代码或一些有用的转换函数 我想到的选项是 1 辅助类 结构中的静态函数 struct helper
  • 以编程方式更改 Windows 服务用户

    我需要以编程方式更改 Windows 服务的登录用户 我使用以下代码来做到这一点 string objPath string Format Win32 Service Name 0 ServiceName using ManagementO

随机推荐

  • 【改进的多同步挤压变换】基于改进多同步挤压的高分辨率时频分析工具,用于分析非平稳信号(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据 文章 1 概述 文献来源 该文提出一种高分辨率时频
  • C++ 文件和流

    iostream 标准库提供了 cin 和 cout 方法 用于从标准输入读取流和向标准输出写入流 而从文件中读取流或向文件写入流 需要用到fstream标准库 在 C 中进行文件处理时 须在源代码文件中包含头文件
  • 基本ACL与高级ACL

    ACL Acess Control List 即访问控制列表 这张表中包含了匹配关系 条件和查询语句 表只是一个框架结构 其目的是为了对某种访问进行控制 信息点间通信 内外网络的通信都是企业网络中必不可少的业务需求 但是为了保证内网的安全性
  • [SQL报错] SQL报错:could not execute statement 和 query did not return a unique result: 2; nested excepti

    错误信息 操作失败 could not execute statement SQL n a nested exception is org hibernate exception DataExcepti 原因是数据库字段长度的限制 还有可能
  • API的使用

    通过API向第3方服务商请求服务 返回数据JSON格式处理成PHP数组格式
  • 机器学习小组知识点7:伯努利分布(Bernouli Distribution)

    适用环境 伯努利分布是较为简单的一种分布 应用于两种实验结果 要么成功 要么失败 一定程度上是二元的性质 这里 我们假设成功的概率为 p p 显然失败的概率就变成了1 p1 p 概率公式可以表示为 f x px 1 p 1 x f x p
  • Python 字符串去除空格的方法

    在处理Python代码字符串的时候 我们常会遇到要去除空格的情况 所以就总结了多种方法供大家参考 1 strip 方法 去除字符串开头或者结尾的空格 str Hello world str strip 输出 Hello world 2 ls
  • 在Unity3D中控制动画播放

    用Unity3D也算是好久了 但是每次做项目总还是能学到新的东西 这次做一个TPS的项目就遇到了这样一个问题 如何同时在上下半身播放不同的动画 解决方法其实是很简单 但由于对于动画资源的了解不足导致问题不断 最后是彻彻底底的研究了一遍Uni
  • myeclipse或sts启动时building workspace加载很长时间

    解决方法 Preference gt General gt Starup and Shutdown勾选Refresh workspace on startup完成 这样每次启动项目时重新加载工作空间 等于重新导入了一份项目 就省去了代码的校
  • esp32 作 MCU 端 使用 AT 命令对 esp8266 进行 OTA demo

    AT CUSTOTA total len current packet len offset checksum OK MCU 收到 gt 之后发送 data 当前数据写入到 FLASH 之后 打印 RECV OK 当接收到 total le
  • 解决Python OpenCV 读取IP摄像头(RTSP等)出现error while decoding的问题

    先来看一个简单的读取RTSP的示例程序 import cv2 cap cv2 VideoCapture rtsp admin admin 123 172 0 0 0 ret frame cap read while ret ret fram
  • 测试两个容器是否连通

    进入容器查看ip root f2b5cdfdc5ed private geth ip addr 1 lo
  • 如何打开Fedora 15命令行窗口CLI

    如何打开Fedora 15命令行窗口CLI 在DesktopFolder以外的桌面区域 右击可以看到Konsole 点击该快捷键 即可启动命令行窗口CLI 第一步 右击桌面 第二步 点击Konsole
  • python day03

    一 使用字符串 str helllo len str 用len函数求字符串长度 str upper 把字符串中的小写变成大写 str find 查找子串所在位置 str index 与find类似但找不到子串时会报错 str 2 从字符串中
  • c++ string函数详细返回值及用法!

    通过在网站上的资料搜集 得到了很多关于string类用法的文档 通过对这些资料的整理和加入一些自己的代码 就得出了一份比较完整的关于string类函数有哪些和怎样用的文档了 下面先罗列出string类的函数有哪一些 然后再罗列出函数的原型
  • 学习 Python 之 Pygame 开发魂斗罗(一)

    学习 Python 之 Pygame 开发魂斗罗 一 Pygame 回忆Pygame 1 使用pygame创建窗口 2 设置窗口背景颜色 3 获取窗口中的事件 4 在窗口中展示图片 1 pygame中的直角坐标系 2 展示图片 3 给部分区
  • 在中国,把区块链玩得转的公司有这几家

    时下最火的Fintech 金融科技 非区块链莫属 区块链正在成为国家层面规划的重点领域之一 区块链可以简单理解为一个由所有参与者公共维护的账本 账本信息的公开使得所有参与者可以一起来校验记账的正确性 使得区块链成为所有参与者可以信任的载体
  • ubuntu编译和安装opencv-3.4.13

    1 安装相关软件包 打开ubuntu 安装以下工具 sudo apt install build essential sudo apt install cmake git libgtk2 0 dev pkg config libavcode
  • 芯片验证从零开始系列(一)——芯片验证概论

    芯片验证从零开始系列 一 芯片验证概论 芯片开发流程 动态验证技术 静态验证技术 Emulation和FPGA原型开发 测试平台框架 检查设计 回归测试 声明 未经作者允许 禁止转载 推荐一个IC FPGA新手入门的好网站 快 点 击 进
  • STL源码剖析-Allocator

    一 第一级配置器 顾名思义 Allocator是用来做内存分配的 第一级配置器指的是当需要128bytes以上的内存时直接使用malloc和realloc进行内存分配 代码如下 第一级配置器 2023 04 07 template