一、内存分配
正如前面所讲的,几乎所有的框架或者库,都会有一个相同的工作,都要对内存进行一次封装,不管是复杂的还是精简的,目的很简单,就是为了让内存在整个框架中更匹配,更不容易出现内存泄露和内存碎片。当然,STL也不例外,在STL的分配器中,分为两大块,一块是默认的分配器,一块儿是自定义的分配器。默认的分配器使用一种尽量平衡的方法来实现对内存分配的管理和回收;而自定义的,则允许用户针对具体的场景,实现自己特定的内存分配和管理手段,至于是不是高效,那就看应用的实际行为了,因为有的是为了方便,或者说其它。
二、STL中内存分配
1、STL默认的内存分配器:
默认的分配器分为两级,第一级为大于128 byte的内存分配,直接调用malloc来分配内存;第二级是一个内存池个数为16个的内存块组成,这些内存块的大小为8的位数,从8~128字节大小。当程序的分配判断符合哪种情况时(向上取整,来判断符合哪个范围),会自动调用相关的处理模块,这个在下面的源码中会进行分析。同样,在释放内存时,也会根据情况来判断是什么情况,来直接回收内存或者返回相关的内存池中。
2、STL自定义内存分配器:
如果在实际的应用开发中需要一种特定的内存分配和回收办法,就可以使用自定义的内存分配器。最常用的是在不同的平台上,可能要求有所不同,比如在嵌入式上,可能对STL的支持不是特别好,就需要定制一些内存的分配器。另外,c++11中智能指针有时会应用到混合的C/C++编程中,对一些C的内存管理,就可以自定义一个内存分配器动态插入到相关的创建函数中。比如在shared_ptr中就提供了deleter这个内存回收器。
另外,内存分配器还有一个重要的作用,当前内存不够使用时,如果有适当的策略进行快速的二次配,保证数据的完整性和内存应用的高效快捷。这些都可以在不同的源码中有不同的体现,毕竟不同的容器,其对内存应用的情况是有很大不同的,这也是把内存分配器抽象出来的一个重要原因。
三、源码分析
看一下分配器的源码:
template<class _Ty>
class allocator
{ // generic allocator for objects of class _Ty
public:
static_assert(!is_const_v<_Ty>,
"The C++ Standard forbids containers of const elements "
"because allocator<const T> is ill-formed.");
using _Not_user_specialized = void;
using value_type = _Ty;
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS typedef _Ty * pointer;
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS typedef const _Ty * const_pointer;
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS typedef _Ty& reference;
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS typedef const _Ty& const_reference;
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS typedef size_t size_type;
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS typedef ptrdiff_t difference_type;
using propagate_on_container_move_assignment = true_type;
using is_always_equal = true_type;
template<class _Other>
struct _CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS rebind
{ // convert this type to allocator<_Other>
using other = allocator<_Other>;
};
_NODISCARD _CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS _Ty * address(_Ty& _Val) const noexcept
{ // return address of mutable _Val
return (_STD addressof(_Val));
}
_NODISCARD _CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS const _Ty * address(const _Ty& _Val) const noexcept
{ // return address of nonmutable _Val
return (_STD addressof(_Val));
}
constexpr allocator() noexcept
{ // construct default allocator (do nothing)
}
constexpr allocator(const allocator&) noexcept = default;
template<class _Other>
constexpr allocator(const allocator<_Other>&) noexcept
{ // construct from a related allocator (do nothing)
}
void deallocate(_Ty * const _Ptr, const size_t _Count)
{ // deallocate object at _Ptr
// no overflow check on the following multiply; we assume _Allocate did that check
_Deallocate<_New_alignof<_Ty>>(_Ptr, sizeof(_Ty) * _Count);
}
_NODISCARD _DECLSPEC_ALLOCATOR _Ty * allocate(_CRT_GUARDOVERFLOW const size_t _Count)
{ // allocate array of _Count elements
return (static_cast<_Ty *>(_Allocate<_New_alignof<_Ty>>(_Get_size_of_n<sizeof(_Ty)>(_Count))));
}
_NODISCARD _CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS _DECLSPEC_ALLOCATOR _Ty * allocate(
_CRT_GUARDOVERFLOW const size_t _Count, const void *)
{ // allocate array of _Count elements, ignore hint
return (allocate(_Count));
}
template<class _Objty,
class... _Types>
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS void construct(_Objty * const _Ptr, _Types&&... _Args)
{ // construct _Objty(_Types...) at _Ptr
::new (const_cast<void *>(static_cast<const volatile void *>(_Ptr)))
_Objty(_STD forward<_Types>(_Args)...);
}
template<class _Uty>
_CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS void destroy(_Uty * const _Ptr)
{ // destroy object at _Ptr
_Ptr->~_Uty();
}
_NODISCARD _CXX17_DEPRECATE_OLD_ALLOCATOR_MEMBERS size_t max_size() const noexcept
{ // estimate maximum array size
return (static_cast<size_t>(-1) / sizeof(_Ty));
}
};
再看一下vector的内存分配:
template<class _Alloc_types>
class _Vector_alloc
{ // base class for vector to hold allocator
public:
using _Alty = typename _Alloc_types::_Alty;
using _Alty_traits = typename _Alloc_types::_Alty_traits;
using _Alproxy = _Rebind_alloc_t<_Alty, _Container_proxy>;
using _Alproxy_traits = allocator_traits<_Alproxy>;
using _Val_types = typename _Alloc_types::_Val_types;
using size_type = typename _Val_types::size_type;
using difference_type = typename _Val_types::difference_type;
using pointer = typename _Val_types::pointer;
using const_pointer = typename _Val_types::const_pointer;
using iterator = _Vector_iterator<_Vector_val<_Val_types>>;
using const_iterator = _Vector_const_iterator<_Vector_val<_Val_types>>;
#if _ITERATOR_DEBUG_LEVEL == 0
_Vector_alloc()
: _Mypair(_Zero_then_variadic_args_t())
{ // default construct allocator
}
template<class _Any_alloc,
class = enable_if_t<!is_same_v<remove_cv_t<remove_reference_t<_Any_alloc>>, _Vector_alloc>>>
_Vector_alloc(_Any_alloc&& _Al)
: _Mypair(_One_then_variadic_args_t(),
_STD forward<_Any_alloc>(_Al))
{ // construct allocator from _Al
}
void _Copy_alloc(const _Alty& _Al)
{ // replace old allocator
_Pocca(_Getal(), _Al);
}
void _Move_alloc(_Alty& _Al)
{ // replace old allocator
_Pocma(_Getal(), _Al);
}
#else /* ^^^ _ITERATOR_DEBUG_LEVEL == 0 ^^^ // vvv _ITERATOR_DEBUG_LEVEL != 0 vvv */
_Vector_alloc()
: _Mypair(_Zero_then_variadic_args_t())
{ // default construct allocator
_Alloc_proxy();
}
template<class _Any_alloc,
class = enable_if_t<!is_same_v<remove_cv_t<remove_reference_t<_Any_alloc>>, _Vector_alloc>>>
_Vector_alloc(_Any_alloc&& _Al)
: _Mypair(_One_then_variadic_args_t(),
_STD forward<_Any_alloc>(_Al))
{ // construct allocator from _Al
_Alloc_proxy();
}
~_Vector_alloc() noexcept
{ // destroy proxy
_Free_proxy();
}
void _Copy_alloc(const _Alty& _Al)
{ // replace old allocator
const bool _Reload = _Alty_traits::propagate_on_container_copy_assignment::value
&& _Getal() != _Al;
if (_Reload)
{
_Free_proxy();
}
_Pocca(_Getal(), _Al);
if (_Reload)
{
_Alloc_proxy();
}
}
void _Move_alloc(_Alty& _Al)
{ // replace old allocator
const bool _Reload = _Alty_traits::propagate_on_container_move_assignment::value
&& _Getal() != _Al;
if (_Reload)
{
_Free_proxy();
}
_Pocma(_Getal(), _Al);
if (_Reload)
{
_Alloc_proxy();
}
}
void _Alloc_proxy()
{ // construct proxy
_Alproxy _Proxy_allocator(_Getal());
_Myproxy() = _Unfancy(_Proxy_allocator.allocate(1));
_Alproxy_traits::construct(_Proxy_allocator, _Myproxy(), _Container_proxy());
_Myproxy()->_Mycont = _STD addressof(_Get_data());
}
void _Free_proxy()
{ // destroy proxy
_Alproxy _Proxy_allocator(_Getal());
_Orphan_all();
_Alproxy_traits::destroy(_Proxy_allocator, _Myproxy());
_Deallocate_plain(_Proxy_allocator, _Myproxy());
_Myproxy() = nullptr;
}
_Iterator_base12 **_Getpfirst() const
{ // get address of iterator chain
return (_Get_data()._Getpfirst());
}
_Container_proxy * & _Myproxy() noexcept
{ // return reference to _Myproxy
return (_Get_data()._Myproxy);
}
_Container_proxy * const & _Myproxy() const noexcept
{ // return const reference to _Myproxy
return (_Get_data()._Myproxy);
}
#endif /* _ITERATOR_DEBUG_LEVEL == 0 */
......
private:
_Compressed_pair<_Alty, _Vector_val<_Val_types>> _Mypair;
};
微软的库不好太好学习,好多定义的宏。但是基本的功能流程在上面可以看得非常清楚。在c++的官网上也标明了相关的c++17和c++20相关函数的更替和加强。而且在新的STL库中,使用了模板的偏特化来实现相关的不同实现的分配器,和上述分析中讲的略有差别,这个在学习的过程中要引起注意,一切以使用的库为标准。
四、实例
看一下自定义的智能指针的一个实例及相关应用的例程(cppreference.com):
template<class T>
inline T* _allocate(ptrdiff_t size, T*) {
std::cout << "call mem allocator" << std::endl;
set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (NULL == tmp) {
std::cerr << "is err!" << std::endl;
exit(0);
}
return tmp;
}
template<class T>
inline void _deallocate(T* p) {
::operator delete(p);
}
template<class T1, class T2>
inline void _construct(T1* p, const T2& value) {
::new(p) T1(value);
}
template<class T>
inline void _destroy(T* p) {
p->~T();
}
template<class T>
class MyAllocator
{
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;
template<class U>
struct rebind
{
typedef MyAllocator<U> other;
};
pointer allocate(size_type n, const void* hint = 0) {
return _allocate((difference_type)n, (pointer)0);
}
void deallocate(pointer p, size_type n) {
return _deallocate(p);
}
void construct(pointer p, const T& value) {
_construct(p, value);
}
void destroy(pointer p) {
_destroy(p);
}
pointer address(reference x) {
return (pointer)&x;
}
const_pointer address(const_reference x) {
return (const_pointer)&x;
}
size_type max_size() const {
return size_type(UINT_MAX / sizeof(T));
}
};
int main()
{
std::vector<int, MyAllocator<int>> v;
v.push_back(3);
v.push_back(6);
for (auto au:v)
{
std::cout << au << std::endl;
}
return 0;
}
输出结果:
call mem allocator
call mem allocator
3
6
注意:这段代码的参考自网上的代码,需要在Linux环境下执行。
五、总结
在c++中内存管理是一个争论不休的话题,包括在Linux内核中,内存管理都是一个重要的问题,内存管理的算法层出不穷。在STL中,可能用不到那么复杂,但是抽象一层分配器,可能会更加快捷方便。对于大内存和小内存的处理会更加安全有效,尽量减少分配的时间和分配中内存碎片的发生。
这本身就是一个永恒的话题,换一个角度看可能就会得出令人难以置信的结果。计算机技术发展到现在,仍然是在各种资源中平衡,这就导致了对资源管理的要求的不断的发展和变化。
拥抱变化吧,只有变化,才是永恒!