从零开始写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 源码解析

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);
}
}

从零开始写STL-内存部分-内存分配器allocator的更多相关文章

  1. 从零开始写STL - 智能指针

    从零开始写STL - 智能指针 智能指针的分类及其特点: scoped_ptr:初始化获得资源控制权,在作用域结束释放资源 shared_ptr: 引用计数来控制共享资源,最后一个资源的引用被释放的时 ...

  2. 从零开始写STL—栈和队列

    从零开始写STL-栈和队列 适配器模式 意图:将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 主要解决:主要解决在软件系统中,常常要将 ...

  3. 从零开始写STL—容器—vector

    从0开始写STL-容器-vector vector又称为动态数组,那么动态体现在哪里?vector和一般的数组又有什么区别?vector中各个函数的实现原理是怎样的,我们怎样使用会更高效? 以上内容我 ...

  4. 从零开始写STL—模板元编程之any

    any class any; (since C++17) The class any describes a type-safe container for single values of any ...

  5. 从零开始写STL—functional

    function C++11 将任意类型的可调用(Callable)对象与函数调用的特征封装到一起. 这里的类是对函数策略的封装,将函数的性质抽象成组件,便于和algorithm库配合使用 基本运算符 ...

  6. 从零开始写STL—哈希表

    static const int _stl_num_primes = 28; template<typename T, typename Hash = xhash<T>> cl ...

  7. 从零开始写STL—模板元编程之tuple

    tuple Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generali ...

  8. 从零开始写STL—set/map

    这一部分只要把搜索树中暴露的接口封装一下,做一些改动. set源码剖析 template<typename T> class set { public: typedef T key_typ ...

  9. STL六大组件之——分配器(内存分配,好深奥的东西)

    SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器:当配置区小于1 ...

随机推荐

  1. mysql 忘记密码 登陆+修改密码

    step1: 苹果->系统偏好设置->最下边点mysql 在弹出页面中 关闭mysql服务(点击stop mysql server) step2: 进入终端输入:cd /usr/local ...

  2. css3中的变换、动画和过渡

    变换:分为2d变换和3d变换,但一次只能用一个变换属性,多个的话最后一个会覆盖前面所有最终被浏览器实现,变换可以成为过渡和动画的一个待变参数(类似width,opacity等). 过渡:是动画的初始模 ...

  3. VM virtualBox网络地址设置

    目的:在虚拟机LINUX中,可用通过主机访问到虚机内容. 问题描述,在虚机系统中,ip地址一直为127.0.0.1,无法在主机中建立连接 参考文章:https://cnzhx.net/blog/vir ...

  4. mybatis 返回值

    转载: 在使用ibatis插入数据进数据库的时候,会用到一些sequence的数据,有些情况下,在插入完成之后还需要将sequence的值返回,然后才能进行下一步的操作.      使用ibatis的 ...

  5. scrapy 的分页爬取 CrawlSpider

    1.创建scrapy工程:scrapy startproject projectName 2.创建爬虫文件:scrapy genspider -t crawl spiderName www.xxx.c ...

  6. 类的封装,property特性,类与对象的绑定方法和非绑定方法,

    类的封装 就是把数据或者方法封装起来 为什么要封装 封装数据的主要原因是:保护隐私 封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了,比如你 ...

  7. jsencrypt加解密 &&cryptico

    npm install --save jsencrypt import {JSEncrypt} from 'jsencrypt'; //导入公钥if ( publicKey.indexOf('---- ...

  8. mitmproxy 数据抓包

    1.安装环境: 基于python windows操作系统需要安装Microsoft Visual C++ V14.0以上 linux操作系统则直接基于python安装即可 2.安装mitmproxy ...

  9. django 标签

    django标签 {% if/for/ifequal/ifnotequal condition %} ...{{ name|first|lower}}{# interpretation:lower t ...

  10. 聚类和EM算法——K均值聚类

    python大战机器学习——聚类和EM算法   注:本文中涉及到的公式一律省略(公式不好敲出来),若想了解公式的具体实现,请参考原著. 1.基本概念 (1)聚类的思想: 将数据集划分为若干个不想交的子 ...