chapter2 空间配置器allocator

1 placement new

new操作符不能重载。如果要实现不同的内存分配行为,需要在类中重载operator new。假如A是一个类,那么A * a=new A实际上执行如下三步:

  1. 调用operator new分配内存,::operator new(sizeof(A))
  2. 调用构造函数生成类对象,A::A()
  3. 返回相应指针

实际上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),否则调用全局::operator new(size_t ),后者由C++默认提供。

void* operator new(std::size_t size, void* pMemory) throw() {return pMemory;}

上述即为new操作符的placement new版本,它允许我们在一个已经分配好的内存中(栈或堆中)构造一个新的对象。原型中void* pMemory实际上就指向一个已经分配好的内存缓冲区的首地址。

#include <iostream>
using namespace std;

class A {
public:
    A(int i) : a(i) {}
    int getVal() {
        return a;
    }
private:
    int a;
};

int main() {
    A* p1 = new A(1);  //在堆上分配
    A A2(2);           //在栈上分配
    A* p2 = &A2;
    cout << "placement前: " << p1->getVal() << " " << p2->getVal() << endl;

    A* p3 = new(p1) A(3); //在p1位置构造
    A* p4 = new(p2) A(4); //在p2位置构造
    cout << "placement前: " << p3->getVal() << " " << p4->getVal() << endl;
}

结果如下:
在这里插入图片描述

2 配置器定义

配置器定义于<memory>中,包含以下两个文件:

#include <stl_alloc.h>		//负责内存空间的配置与释放
#include <stl_construct.h>	//负责对象内容的构造与析构

在这里插入图片描述

3 构造和析构的基本工具:construct和destory函数

construct函数接受一个指针和一个初值,以将初值设定到指针所指向的空间。主要是使用placement new来为已经分配好的内存空间创造类对象,并调用类构造函数来进行初始化。

destory函数有两种版本,第一种接受一个指针,调用指针所指向的类对象的析构函数;第二种接受first和last两个迭代器,并根据迭代器所指向对象的型别来判断要释放的迭代器的指向对象有没有trivial destructor,如果有trivial destructor则什么都不做,如果没有即需要循环析构掉[first, last)范围内的所有对象。
在这里插入图片描述

4 空间的配置与释放:std::alloc

基于__USE_MALLOC宏是否被定义,判断alloc为第一级配置器还是第二级配置器;并将之封装在一个接口类simple_alloc中,通过接口类中的成员函数来调用配置器。
在这里插入图片描述

5 第一级配置器__malloc_alloc_template

第一级配置器使用malloc(),realloc()分配内存,free()释放内存。并且,当内存分配失败时,会采用类似C++ new-handler的机制,改调用oom_malloc()和oom_realloc(),通过不断的内循环来调用“内存不足处理例程”,但这种函数并未在__malloc_alloc_template类中具体实现,需要由客户端设定,否则会直接抛出异常。

6 第二级配置器__default_alloc_template

第二级配置器通过增加一些机制,避免太多小额区块造成内存的碎片以及额外负担问题。当区块足够大,超过128bytes时,就移交第一级配置器处理。当区块小于128bytes时,就以内存池进行管理,即次级配置:每次配置一大块内存,并维护对应的自由链表(free-list)。下次若有相同 内存需求,就直接从free-lists中拨出。如果客户端释还小额区块,就由配置器回收到free-lists中。为方便管理,第二级配置器会将小额区块的内存需求量上调至8的倍数,并维护16个free-lists,各自管理大小从8、16、24、… 、128bytes的小额区块。

6.1 free-lists的节点构造(union联合体)

union obj {
    union obj * free_list_link;	//指向下一个节点(区块)
    char client_data[1];		//区块大小
}

联合体中的各个成员共享内存空间,减少内存开销。

#include <iostream>
using namespace std;

union obj {
    union obj* ptr;
    char data[1];
};

int main() {
    obj* p1 = (obj*)malloc(10);  //内存块1
    p1->ptr = (obj*)malloc(20);  //内存块2
    cout << p1 << endl;
    cout << p1->ptr << endl;

    delete p1->ptr;
    delete p1;
}

在这里插入图片描述

6.2 自由链表free_list

static obj * volatile free_list[_NFREELISTS]

volatile声明变量值时,系统总是重新从它所在的内存读取数据。它会影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。

free_list数组中的每一个都是一个链表的头指针,总共有16个链表,对应于8、16、24、… 、128bytes的小额区块。

6.3 相关函数

6.3.1 空间配置函数allocate()

  1. 判断区块大小,大于128bytes就调用第一级配置器,小于128bytes就检查对应的free list。
  2. 如果 free list 有可用的区块,就直接拿来用,如果没有可用区块,就将区块大小上调至 8 倍数边界,然后调用refill(),重新填充free list。
  3. 并且在有区块拨出时,调整free list。

6.3.2 空间释放函数deallocate()

  1. 首先判断区块大小,大于 128 bytes 就调用第一级配置器,小于 128 bytes 就找出对应的 free list,将区块回收

6.3.3 重新填充free lists函数refill()

  1. 当 free list 中没有可用区块时,就调用refill()函数,为 free list 重新填充空间,新的空间取自内存池,默认取得 20 个新节点(新区块),但如果内存池空间不足,获得的节点数(区块数)可能小于20;
  2. 如果只获得一个区块,这个区块就分配给调用者用,free list 无新节点;否则从1(第0个返回客户端)开始准备调整 free list,纳入新节点。

6.3.4 chunk_alloc()函数

  1. 给内存池分配空间以及ongoing内存池中取空间给free list使用

在这里插入图片描述

7 内存基本处理工具

7.1 uninitialized_copy

template <class InputIterator, class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first, ForwardIterator ;ast, ForwardIterator result);

该函数会使用copy constructor将迭代器 [first, last)范围内的每一个对象复制到从迭代器result开始指向的未构造内存中。即针对输入范围内的每个迭代器i,该函数会调用construct(&*(result + (i - first)), *i)产生*i的复制品,放置于输出范围的相对位置上。

参数:

  1. 迭代器first指向输入端的起始位置
  2. 迭代器last指向输入端的结束位置(前闭后开区间)
  3. 迭代器result指向输出端(欲初始化空间)的起始处

7.2 uninitialized_fill

template <class ForwardIterator, class T>
void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x)

如果 [first, last) 范围内的每一个迭代器都指向未初始化内存,那么该函数会在该范围内产生 x 的复制品。即针对操作范围内的每个迭代器i,调用construct(&*i, x),在i所指之处产生x的复制品。

  1. 迭代器first指向输出端(欲初始化空间)的起始位置
  2. 迭代器last指向输出端(欲初始化空间)的结束位置(前闭后开区间)
  3. x表示初值

7.3 uninitialized_fill_n

template <class ForwardIterator, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x)

如果 [first, first+n) 范围内的每一个迭代器都指向未初始化内存,那么该函数会调用copy constructor,在该范围内产生 x 的复制品。即面对[first, first + n)范围内的每个迭代器i, uninitialized_fill_n()会调用construct(&*i, x),在对应位置处产生x的复制品。

参数:

  1. 迭代器first指向欲初始化空间的起始处
  2. n表示欲初始化空间的大小
  3. x表示初值

7.4 函数实现逻辑

萃取迭代器的value type,然后判断该型别是否为POD型别(Plain Old Data,标量型别或传统的C struct型别)。对于POD型别采用最有效率的初值填写手法,即交由高阶函数(STL算法)执行;对于non-POD型别采取最保险安全的做法,即一个一个元素地构造,无法批量进行。

针对char* 和 wchar_t*两种型别,可以采用最有效率的做法memmove(直接移动内存内容)来执行复制行为。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值