zoukankan      html  css  js  c++  java
  • 从零开始写STL-内存部分-内存分配器allocator

    从零开始写STL-内存部分-内存分配器allocator

    内存分配器是什么?

    一般而言,c++的内存分配和释放是这样操作的

    class Foo{ //...};
    Foo* pf = new Foo;//配置内存,然后建构对象
    delete pf; //将对象解构,然后释放内存
      其中的 new操作内含两阶段动作:(1)调用::operator new配置内存,(2) 调用Foo::Foo()建构对象内容。delete操作也内含两阶段动作: (1)调用Foo::~Foo()将对象解构,(2)调用::operator delete释放内存。
      为了精密分工,STL allocator决定将这两阶段区分开来。内存配置由alloc:allocate()负责,内存释放由alloc::deallocate()负责; 对象建构由::construct()负责,对象析构由::destroy()负责。

    题外话 对于new 和 delete

    为了避免对后面析构函数 和 内存回收的部分产生一些基本疑问,对new 和 delete做一些总结

    • new 的调用过程
      new -> operator new -> malloc -> 构造函数
    • operator new 源码解析
      avatar

    construct 与 destory

    //在分配好的内存上调用T1类的构造参数
    //T2 应该是能被T1类型的构造参数接收的类型或者可以隐式转换为可接受类型的值
    template<class T1, class T2>
    inline void construct(T1* p, const T2& value)
    {
    	new(p) T1(value);//调用placement new 
    	// 在已经分配好的内存上调用构造函数,不能用delete释放
    }
    
    template<class T>
    inline void destroy(T* ptr)
    {
    	ptr->~T();//泛型析构
    }
    

    allocator 源码分析

    可以看到内存的分配是通过alloc函数来进行的,进行指针类型转换之后调用对应的泛型构造和析构函数。

    namespace ministl
    {
    	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;
    	public:
    		static T *allocate();
    		static T *allocate(size_t n);
    		static void deallocate(T *ptr);
    		static void deallocate(T *ptr, size_t n);
    
    		static void construct(T *ptr);
    		static void construct(T *ptr, const T& value);
    		static void destroy(T *ptr);
    		static void destroy(T *first, T *last);
    	};
    
    	template<class T>
    	T *allocator<T>::allocate() {
    		return static_cast<T *>(alloc::allocate(sizeof(T)));//指针类型转换
    	}
    	template<class T>
    	T *allocator<T>::allocate(size_t n) {
    		if (n == 0) return 0;
    		return static_cast<T *>(alloc::allocate(sizeof(T) * n));
    	}
    	template<class T>
    	void allocator<T>::deallocate(T *ptr) {
    		alloc::deallocate(static_cast<void *>(ptr), sizeof(T));
    	}
    	template<class T>
    	void allocator<T>::deallocate(T *ptr, size_t n) {
    		if (n == 0) return;
    		alloc::deallocate(static_cast<void *>(ptr), sizeof(T)* n);
    	}
    
    	template<class T>
    	void allocator<T>::construct(T *ptr) {
    		new(ptr)T();
    	}
    	template<class T>
    	void allocator<T>::construct(T *ptr, const T& value) {
    		new(ptr)T(value);
    	}
    	template<class T>
    	void allocator<T>::destroy(T *ptr) {
    		ptr->~T();
    	}
    	template<class T>
    	void allocator<T>::destroy(T *first, T *last) {
    		for (; first != last; ++first) {
    			first->~T();
    		}
    	}
    }
    

    真正的底层内存分配器 Alloc

    Alloc的内存分配分为两级,一级是大于128KB的内存块管理,直接通过malloc 和 free来进行,小于128KB的内存管理,是通过维护一个内存池来实现的。

    	class alloc {
    	private:
    		enum EAlign { ALIGN = 8 };//小型区块的上调边界
    		enum EMaxBytes { MAXBYTES = 128 };//小型区块的上限,超过的区块由malloc分配
    		enum ENFreeLists { NFREELISTS = (EMaxBytes::MAXBYTES / EAlign::ALIGN) };//free-lists的个数
    		enum ENObjs { NOBJS = 20 };//每次增加的节点数
    	private:
    		//free-lists的节点构造
    		//节省内存你,既可以用来存储数据也可以用来存储指向下一个节点的指针
    		union obj {
    			union obj *next;
    			char client[1];
    		};
    
    		static obj *free_list[ENFreeLists::NFREELISTS];
    	private:
    		static char *start_free;//内存池起始位置
    		static char *end_free;//内存池结束位置
    		static size_t heap_size;//
    	private:
    		//将bytes上调至8的倍数
    		static size_t ROUND_UP(size_t bytes) {
    			return ((bytes + EAlign::ALIGN - 1) & ~(EAlign::ALIGN - 1));
    		}
    		//根据区块大小,决定使用第n号free-list,n从0开始计算
    		static size_t FREELIST_INDEX(size_t bytes) {
    			return (((bytes)+EAlign::ALIGN - 1) / EAlign::ALIGN - 1);
    		}
    		//返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list
    		static void *refill(size_t n);
    		//配置一大块空间,可容纳nobjs个大小为size的区块
    		//如果配置nobjs个区块有所不便,nobjs可能会降低
    		static char *chunk_alloc(size_t size, size_t& nobjs);
    
    	public:
    		static void *allocate(size_t bytes);
    		static void deallocate(void *ptr, size_t bytes);
    		static void *reallocate(void *ptr, size_t old_sz, size_t new_sz);
    	};
    

    静态初始化

    
    	char *alloc::start_free = 0;
    	char *alloc::end_free = 0;
    	size_t alloc::heap_size = 0;
    	//是一个链表数组
    	alloc::obj *alloc::free_list[alloc::ENFreeLists::NFREELISTS] = {
    		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    	};
    

    allocate 与 deallocate

    这一部分是alloc 暴露的外部接口,通过找到当前free_list中第一个满足要求内存块大小的内存,从链表头取出返回,如果是释放内存就重新插到对应链表头上。
    注意这里的链表头 表示的是大于多少K的节点 比如大于64却小于512的内存块

    	void *alloc::allocate(size_t bytes) {
    		if (bytes > EMaxBytes::MAXBYTES) {
    			return malloc(bytes);//直接调用malloc
    		}
    		size_t index = FREELIST_INDEX(bytes);
    		obj *list = free_list[index];
    		if (list) {//此list还有空间给我们
    			free_list[index] = list->next;
    			return list;
    		}
    		else {//此list没有足够的空间,需要从内存池里面取空间
    			return refill(ROUND_UP(bytes));
    		}
    	}
    	void alloc::deallocate(void *ptr, size_t bytes) {
    		if (bytes > EMaxBytes::MAXBYTES) {
    			free(ptr);
    		}
    		else {
    			size_t index = FREELIST_INDEX(bytes);
    			obj *node = static_cast<obj *>(ptr);
    			node->next = free_list[index];
    			free_list[index] = node;
    		}
    	}
    	void *alloc::reallocate(void *ptr, size_t old_sz, size_t new_sz) {
    		deallocate(ptr, old_sz);
    		ptr = allocate(new_sz);
    
    		return ptr;
    	}
    

    内部的内存管理

    refill负责对内存池中取出的对象做处理
    chunk_alloc 负责从内存池中取出对应大小的内存块

    	//返回一个大小为n的对象,并且有时候会为适当的free list增加节点
    	//假设bytes已经上调为8的倍数
    	void *alloc::refill(size_t bytes) {
    		size_t nobjs = ENObjs::NOBJS;
    		//从内存池里取,会改变nobjs的值
    		char *chunk = chunk_alloc(bytes, nobjs);
    		obj **my_free_list = 0;
    		obj *result = 0;
    		obj *current_obj = 0, *next_obj = 0;
    
    		if (nobjs == 1) {//取出的空间只够一个对象使用
    			return chunk;
    		}
    		else {//取出内存块较大 需要进行回收
    			my_free_list = free_list + FREELIST_INDEX(bytes);
    			result = (obj *)(chunk);
    			*my_free_list = next_obj = (obj *)(chunk + bytes);
    			//将取出的多余的空间加入到相应的free list里面去
    			for (int i = 1;; ++i) {
    				current_obj = next_obj;
    				next_obj = (obj *)((char *)next_obj + bytes);
    				if (nobjs - 1 == i) {
    					current_obj->next = 0;
    					break;
    				}
    				else {
    					current_obj->next = next_obj;
    				}
    			}
    			return result;
    		}
    	}
    	//假设bytes已经上调为8的倍数
    	char *alloc::chunk_alloc(size_t bytes, size_t& nobjs) {
    		char *result = 0;
    		size_t total_bytes = bytes * nobjs;
    		size_t bytes_left = end_free - start_free;
    
    		if (bytes_left >= total_bytes) {//内存池剩余空间完全满足需要
    			result = start_free;
    			start_free = start_free + total_bytes;
    			return result;
    		}
    		else if (bytes_left >= bytes) {//内存池剩余空间不能完全满足需要,但足够供应一个或以上的区块
    			nobjs = bytes_left / bytes;
    			total_bytes = nobjs * bytes;
    			result = start_free;
    			start_free += total_bytes;
    			return result;
    		}
    		else {//内存池剩余空间连一个区块的大小都无法提供
    			size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
    			if (bytes_left > 0) {//现有剩余内存加入内存池
    				obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
    				((obj *)start_free)->next = *my_free_list;
    				*my_free_list = (obj *)start_free;
    			}
    			start_free = (char *)malloc(bytes_to_get);
    			if (!start_free) {
    				obj **my_free_list = 0, *p = 0;
    				for (int i = 0; i <= EMaxBytes::MAXBYTES; i += EAlign::ALIGN) {
    					my_free_list = free_list + FREELIST_INDEX(i);
    					p = *my_free_list;
    					if (p != 0) {
    						*my_free_list = p->next;
    						start_free = (char *)p;
    						end_free = start_free + i;
    						return chunk_alloc(bytes, nobjs);
    					}
    				}
    				end_free = 0;
    			}
    			heap_size += bytes_to_get;
    			end_free = start_free + bytes_to_get;
    			return chunk_alloc(bytes, nobjs);
    		}
    	}
    
    
  • 相关阅读:
    SkyWalking结合Logback获取全局唯一标识 trace-id 记录到日志中
    Mysql数据库优化技术
    MySQL中集合的差的运算方法
    深入理解Java ClassLoader及在 JavaAgent 中的应用
    自制吸锡带
    Ubuntu下双显示器设定
    ffmpeg 命令的使用
    ifeq ifneq ifdef ifndef
    字符对齐
    ruby on rails使用gmail的smtp发送邮件
  • 原文地址:https://www.cnblogs.com/joeylee97/p/8656510.html
Copyright © 2011-2022 走看看