NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
# 第2章 空间配置器 ## 2.1 空间配置器第标准接口 根据 STL 的规范,以下是 allocator 的必要接口: ```c++ #include <cstddef> // for ptrdiff_t, size_t 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; // 一个嵌套的class template template <class U> struct rebind { typedef allocator<U> other; }; // 返回某个对象的地址,算式 a.address(x) 等同于 &x pointer address(reference x) const; // 返回某个 const 对象的地址,算式 a.address(x) 等同于 &x const_pointer address(const_reference x) const; // 配置空间,足以储存 n 个 T 对象,第二个参数是个提示 pointer allocate(size_type n, const void* = 0); // 归还先前配置的空间 void deallocate(pointer p, size_type n); // 返回可成功配置的最大量 size_type max_size() const; // 等同于 new((void*)p) T(x) void construct(pointer p, const T& x); // 等同于 p->~T() void destroy(pointer p); }; ``` ## 2.2 具备次配置力(sub-allocation)的 SGI 空间配置器 SGI 的配置器与众不同,也与标准规范不同,其名称是 alloc 而非 allocator,而且不接受任何参数: ```c++ vector<int, std::allocator<int> > iv; // in VC or CB; vector<int, std::alloc> iv; // in GCC ``` ### 2.2.1 SGI 标准的空间配置器,std::allocator SGI 也定义有一个符合部分标准、名为 allocator 的配置器,但 SGI 从未使用过,主要原因是效率不佳,它只是把 C++ 的 `::operator new` 和 `::operator delete` 做了一层薄薄的包装。 ### 2.2.2 SGI 特殊的空间配置器,std::alloc 通常 C++ 内存配置操作和释放操作通过 `new` 和 `delete` 两个运算符实现,而 `new` 算式内含两阶段操作:(1) 调用 `::operator new` 配置内存;(2) 调用对应对象的构造函数构造对象内容。 `delete` 算式也内含两个阶段操作:(1) 调用对应对象的析构函数将对象析构;(2) 调用 `::operator delete` 释放内存。 为了精密分工,STL allcoator 将两阶段操作区分开。内存配置操作由 `alloc::allocate()` 负责,内存释放操作由 `alloc::deallocate()` 负责,对象构造由 `::construct()`负责,对象析构 `::destroy()` 负责。 ### 2.2.3 构造和析构基本工具:construct() 和 destroy() `costruct()` 接受一个指针 `p` 和一个初值 `value`,该函数的用途就是将初值设定到指针所指空间上。 ``` c++ template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p) T1(value); } ``` `destroy()` 有两个版本,第一个版本接受一个指针,准备将该指针所指向的对象析构,第二个版本接受 `first` 和 `last` 两个迭代器,准备将 `[first, last)` 范围内的所有对象析构掉。 ```c++ template <class T> inline void destroy(T* pointer) { pointer->~T(); } template <class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(frist, last, value_type(first)); } ``` 其中第二版本的 `destroy()` 会先通过 `value_type()` 判断当前类型不调用析构函数是否有影响,如果无影响则不调用,否则逐一调用对象的析构函数。 ### 2.2.4 空间的配置与释放,std::alloc 对象构造前的空间配置和对象析构后的空间释放有以下要求: * 向 system heap 要求空间; * 考虑多线程(multi-threads)状态; * 考虑内存不足时的应变措施; * 考虑过多“小型区块“可能造成的内存碎片(fragment)问题; 考虑到小型区块可能造成的内存破碎问题,SGI 设计了双层级配置器,第一级配置器直接使用 `malloc()` 和 `free()` ,第二级配置器则视情况采用不同策略:当配置区块超过 128 bytes 时,便调用第一级配置器;当配置块小于 128 bytes 时,为了降低额外负担,便采用复杂的 memory pool整理。 ### 2.2.5 第一级配置器 __malloc_alloc_template 剖析 第一级配置器以 `malloc()`,`free()`,`realloc()` 等 C 函数执行实际的内存配置、释放、重配置操作,并实现出类似 C++ new-handler 的机制。 ### 2.2.6 第二级配置器 __default_alloc_template 剖析 次级配置(sub-allocation)每次配置一块大内存,并维护对应的自由链表(free-list)。为了方便管理,SGI 第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数,并维护 16 个 free-list,各自管理 8、16、24、32、40、48、56、64、72、80、88、96、104、112、128 bytes 的小额区块。 ### 2.2.7 空间配置函数 allocate() ```c++ static void* allocate(size_t n); ``` * 首先判断区块大小是否大于128 bytes,如果大于则使用第一级配置器; * 如果小于128bytes,寻找16个free-list中适当的一个,如果对应free-list有可用区块,则使用该区块; * 如果没有可用区块,就将区块上调至8的倍数边界,然后调用 `refill()` 准备为 free-list 重新填充空间。 ### 2.2.8 空间释放函数 deallocate() ```c++ static void deallocate(void* p, size_t n); ``` * 首先判断区块是否大于 128 bytes,如果大于则使用第一次配置器释放对应空间; * 如果小于 128 bytes,则将空间归还给对应的free-list; ### 2.2.9 重新填充 free lists,refill() 调用 `chunk_alloc()` 获取新的空间,然后将新获取的空间装入对应的free-list。 ``` c++ template <bool threads, int inst> void* __default_alloc_template<threads, inst>::refill(size_t n); ``` ### 2.2.10 内存池(memory pool) ```c++ template <bool threads, int inst> char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs); ``` * 内存池中剩余空间完全满足,则从剩余空间中分配; * 内存池中剩余空间只够满足部分区块,则从剩余空间中分配可满足的数量; * 内存池中剩余空间连一个区块也无法满足: * 将剩余的空间(零头)分配给适当的free-list; * 从heap中获取空间补充内存池; * 如果从heap中配置失败,则从尝试从更大的free-list中“借用“空间; * 如果free-list中也无合适的空间,则分配失败; ## 2.3 内存基本处理工具 ```c++ template <class InputIterator, class ForwardIterator> ForwardIterator uninitialized_copy(InputIterator first, IntputIterator last, ForwardIterator result); ``` `uninitialized_copy()` 针对输入范围内的每一个迭代器 `i`,该函数会调用 `construct(&*(result+(i-frist)), *i)`,产生 `*i` 的复制品。 ```c++ template <class ForwardIterator, class T> void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T&x); ``` `uninitialized_fill()` 针对操作范围内的每个迭代器 `i`,调用 `construct(&*i, x)`。 ```c++ template <class ForwardIterator, class Size, class T> ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x); ``` `uninitialized_fill_n()` 面对 `[first, first+n)` 范围内的每个迭代器,调用 `construct(&*i, x)`。