配置器之隐藏的内存分配
1 前言
配置器(allocators)是STL提供的溜达组件之一,负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。由于配置器的存在,我们使用的容器的内存管理都被隐藏起来了。
本文主要先简单介绍一下SGI STL然后重点分析标准STL。
2 SGI STL
SGI STL配置器的定义在 defalloc.h 文件中。
源码分析:见注释
// 分配函数实现比较简单,就是对::operator new做一个简单封装
template <class T>
inline T* allocate(ptrdiff_t size, T*) {
set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)))); // 分配内存
if (tmp == 0) {
cerr << "out of memory" << endl;
exit(1);
}
return tmp;
}
// 释放内存函数,对 ::operator delete 作一层封装
template <class T>
inline void deallocate(T* buffer) {
::operator delete(buffer);
}
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;
// 空间分配函数
pointer allocate(size_type n) {
return ::allocate((difference_type)n, (pointer)0);
}
// 释放空间函数
void deallocate(pointer p) { ::deallocate(p); }
pointer address(reference x) { return (pointer)&x; }
const_pointer const_address(const_reference x) {
return (const_pointer)&x;
}
size_type init_page_size() {
return max(size_type(1), size_type(4096/sizeof(T)));
}
size_type max_size() const {
return max(size_type(1), size_type(UINT_MAX/sizeof(T)));
}
};
// 模板特化
class allocator<void> {
public:
typedef void* pointer;
};
SGI STL定义了一个符合部分标准的allocator配置器,但是SGI并为使用过,也不建议使用,主要因为效率不佳,其只是把C++的::operator new和::operator delete做一层简单地封装而已。
3 STL标准版本
3.1 new 和 delete
new操作符:内存分配和对象构造组合在一起的。
- 1.先调用::operator new配置内存
- 2.然后调用构造函数构造对象
delete操作符:
- 1.先调用析构函数将对象析构
- 2.再调用::operator delete释放内存
allocator 将内存分配和对象构造进行了分离
3.2 allocator类中关键的接口
allocator a | 定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存 |
---|---|
a.allocate(n) | 分配一段原始的,未构造的内存,保存n个类型为T的对象 |
a.deallocate(p,n) | 释放从T指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy a.construct(p,args) p必须是一个类型为T |
a.construct(p,args) | p必须是一个类型为T* 的指针,指向一块原始内存;args被传递给类型为T的构造对象,用来在p指向的内存中构造一个对象 |
a.destroy§ | p为T*类型的指针,此算法对p指向的对象执行析构函数 |
3.3 源码分析
// stl_alloc.h 关键定义
template <class _Tp>
class allocator {
// typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef alloc _Alloc; // The underlying allocator.
public:
// 各种类型重定义
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Tp* pointer;
// ...
// 内存分配
_Tp* allocate(size_type __n, const void* = 0) {
return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp)))
: 0;
}
// 内存释放
void deallocate(pointer __p, size_type __n)
{
_Alloc::deallocate(__p, __n * sizeof(_Tp));
}
// 在原始内存上构造对象
void construct(pointer __p, const _Tp& __val) { new(__p) _Tp(__val); }
// 释放内存上的对象,成为原始内存(未初始化)
void destroy(pointer __p) { __p->~_Tp(); }
};
3.3 allocator算法
分配关键算法 | 介绍 |
---|---|
uninitialized_copy(b,e,b2) | 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中,b2指向的内存必须足够大,能容纳输入序列中元素的拷贝 |
uninitialized_copy_n(b,n,b2) | 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中 |
uninitialized_fill(b,e,t) | 在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝 |
uninitialized_fill_n(b,n,t) | 从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象 |
这些算法主要是实现内存的填充与拷贝。
3.4 memory中关于配置器的重要头文件
头文件 | 介绍 |
---|---|
stl_construct.h | 这里定义了全局函数construct() 和 destroy(),负责对象的构造和析构,它们也都隶属于STL标准规范。 |
stl_alloc.h | 定义了一二级配置器,彼此合作,配置器名为alloc |
stl_uninitialized.h | 定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,它们也都隶属于STL标准规范。 |
// stl_construct.h 关键定义
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
new ((void*) __p) _T1(__value);
}
// construct 构造对象
template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
_Construct(__p, __value);
}
template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
__pointer->~_Tp();
}
// 析构对象
template <class _Tp>
inline void destroy(_Tp* __pointer) {
_Destroy(__pointer);
}
// stl_uninitialized.h 关键实现
// 拷贝
inline char* uninitialized_copy(const char* __first, const char* __last,
char* __result) {
memmove(__result, __first, __last - __first);
return __result + (__last - __first);
}
template <class _InputIter, class _Size, class _ForwardIter>
inline pair<_InputIter, _ForwardIter>
uninitialized_copy_n(_InputIter __first, _Size __count,
_ForwardIter __result) {
return __uninitialized_copy_n(__first, __count, __result,
__ITERATOR_CATEGORY(__first));
}
// 填充
template <class _ForwardIter, class _Tp>
inline void uninitialized_fill(_ForwardIter __first,
_ForwardIter __last,
const _Tp& __x)
{
__uninitialized_fill(__first, __last, __x, __VALUE_TYPE(__first));
}
template <class _ForwardIter, class _Size, class _Tp>
inline _ForwardIter
uninitialized_fill_n(_ForwardIter __first, _Size __n, const _Tp& __x)
{
return __uninitialized_fill_n(__first, __n, __x, __VALUE_TYPE(__first));
}
template <class _ForwardIter, class _Tp, class _Tp1>
inline void __uninitialized_fill(_ForwardIter __first,
_ForwardIter __last, const _Tp& __x, _Tp1*)
{
typedef typename __type_traits<_Tp1>::is_POD_type _Is_POD;
__uninitialized_fill_aux(__first, __last, __x, _Is_POD());
}
template <class _ForwardIter, class _Tp>
void __uninitialized_fill_aux(_ForwardIter __first, _ForwardIter __last,
const _Tp& __x, __false_type)
{
_ForwardIter __cur = __first;
__STL_TRY {
for ( ; __cur != __last; ++__cur)
_Construct(&*__cur, __x);
}
__STL_UNWIND(_Destroy(__first, __cur));
}
4 参考博客
(19条消息) 你不能不知道的内存分配,从全局概览STL的allocator空间配置器_stl中map的allocator_董哥的黑板报的博客-CSDN博客