STL源码:分配器 allocator

operator new() 和 malloc()

在这里插入图片描述
operator new()就是调用malloc来申请内存空间
所有的分配内存操作最终都将落在 malloc 上。malloc分配的实际内存要比申请的内存大,因为有附加信息,附加信息一般是固定的

allocators(VC6 BC5 G2.9 G4.9)

  1. VC6版本
    在这里插入图片描述

  2. BC5版本

在这里插入图片描述

  1. G2.9版本

在这里插入图片描述
G2.9虽然定义了和其他版本分配器类似的<defalloc.h>,但是未使用它。G2.9使用了一种改进的allocator,即<stl_alloc.h>

allocator的目标:尽量减少malloc的次数、减少额外开销。<stl_alloc.h>能够减少额外开销:

在这里插入图片描述

减少额外开销:
额外开销来自于对该块空间的大小等的说明(Cookie),G2.9通过以下方法节省额外开销:
G2.9实际上的allocator是维护n个链表,每一个链表负责某一个特定大小的区块,如第1个链表1个字节的分配,第2个负责2个字节的分配,第3个负责3个字节的分配…没有时再去和操作系统申请并切割
同一个链表上的内存块可以省掉一些额外开销,如大小等信息就不必再加上

  1. G4.9版本(最新版本)

G4.9又摒弃了G2.9的设计,用了无特殊设计的VC6模式,如下

在这里插入图片描述

但G2.9版本也被保留了

在这里插入图片描述

allocator使用方法

int main() {
    std::allocator<std::string> alloc; // 可以分配string的allocator对象
    
    int n{5};
    auto const p = alloc.allocate(n); // 分配n个未初始化的string

    auto q = p;
    alloc.construct(q++); // *q为空字符串
    alloc.construct(q++, 10, 'o'); // *q为cccccccccc
    alloc.construct(q++, "hello"); // *q为hello

    std::cout << *p << std::endl; // 正确:使用string的输出运算符
    //std::cout << *q << std::endl; // 错误:q指向未构造的内存
    std::cout << p[0] << std::endl;
    std::cout << p[1] << std::endl;
    std::cout << p[2] << std::endl;

    while (q != p) {
        alloc.destroy(--q); // 释放我们真正构造的string
    }

    alloc.deallocate(p, n);

    return 0;
}

allocator源码详解

源文件结构

这里面还有很多底层函数没找到实现在哪里,只能先看大概

1. 在QT5.8中,vector定义如下

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>{}

template<typename _Tp, typename _Alloc>
struct _Vector_base{}

默认使用std::allocator分配器

2. allocator结构说明

在这里插入图片描述

(1)以qt5.8的allocator.h为例,最上一层定义了多个allocator,其中一个如下

在这里插入图片描述

(2)可以看到上面的allocator类中没有标准规定的deallocator等函数。但是它继承了基类**__allocator_base**,而该基类出现在c++allocator.h中,如下:

template<typename _Tp>
using __allocator_base = __gnu_cxx::new_allocator<_Tp>;

(3)进一步找到**<new_allocator.h>**文件,可以发现里面定义了allocator所需要的所有必须类,包括在deallocator在内。___gnu_cxx::new_allocator<_Tp>是大部分编译器实现allocator的最底层
在这里插入图片描述

allocator基类:new_allocator

参考:https://zhuanlan.zhihu.com/p/354191253

https://blog.csdn.net/mei_true/article/details/113951160

c++默认的内存分配器继承于___gnu_cxx::new_allocator<_Tp>,它有两个任务

  • 分配对象内存、初始化对象
  • 析构对象、释放对象内存

源码如下:

//使用std中size_t和ptrdiff_t定义,为了程序的可移值性适应不同平台
//size_t表示unsigned类型,ptrdiff_t保存两个指针相减的结果
using std::size_t;
using std::ptrdiff_t;
//1. 定义new_allocator模板
template<typename _Tp>
class new_allocator{
public:
    typedef size_t     size_type;//类型定义,__allocator_traits中介绍
    typedef ptrdiff_t  difference_type;//元素之间的距离
    typedef _Tp*       pointer;//指针类型
    typedef const _Tp* const_pointer;
    typedef _Tp&       reference;//左值引用类型(左值就是非临时变量)
    typedef const _Tp& const_reference;
    typedef _Tp        value_type;//元素类型
	
    //2. rebind,结构体模板(重要函数)
    template<typename _Tp1>
    struct rebind{ 
        typedef new_allocator<_Tp1> other; 
    };
	
    //3. 检测是否支持C++11标准;里面的变量含义将在后面内存学习中详细解答
    #if __cplusplus >= 201103L
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 2103. propagate_on_container_move_assignment
    typedef std::true_type propagate_on_container_move_assignment;
    #endif
	
    //4. 无参构造,_GLIBCXX_USE_NOEXCEPT被define为noexcept,表示不会抛出异常
    new_allocator() _GLIBCXX_USE_NOEXCEPT { }

    //5. 拷贝构造函数
    new_allocator(const new_allocator&) _GLIBCXX_USE_NOEXCEPT { }
	
    //6. 构造函数模板,泛化的拷贝构造
    template<typename _Tp1>
    new_allocator(const new_allocator<_Tp1>&) _GLIBCXX_USE_NOEXCEPT { }
	
    //7. 析构函数
    ~new_allocator() _GLIBCXX_USE_NOEXCEPT { }
	
    //8. 定义address成员函数,refence是_Tp&类型,__addressof是当存在operator&的重载时, 依然能够获得变量的地址
    //算式a.address(x)等同于&x
    pointer address(reference __x) const _GLIBCXX_NOEXCEPT { 
        return std::__addressof(__x); 
    }
	
    //9. 和上面的函数类似,只是采用const类型
    const_pointer address(const_reference __x) const _GLIBCXX_NOEXCEPT { 
        return std::__addressof(__x); 
    }

    // NB: __n is permitted to be 0.  
    // The C++ standard says nothing about what the return value is when __n == 0.
    //10. 分配内存(重要函数)
    pointer allocate(size_type __n, const void* = 0){
        if (__n > this->max_size())
            std::__throw_bad_alloc();
        return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
    }

    // __p is not permitted to be a null pointer.
    //11. 释放内存(重要函数)
    void deallocate(pointer __p, size_type){ 
        ::operator delete(__p); 
    }
	
    //12. 返回能分配的最大内存
    //size_t对32位机来说是32位,对64位机来说是64位,size_t(-1)恰能表达其能代表的最大值(补码+1刚好所有位都为1)
    //suze_t/sizeof(_Tp)就能表达在当前平台下所能返回的最大_Tp个数(理论上是4G,但实际上达不到)
    size_type max_size() const _GLIBCXX_USE_NOEXCEPT { 
        return size_t(-1) / sizeof(_Tp); 
    }
	//13. construct和destory
    #if __cplusplus >= 201103L
    template<typename _Up, typename... _Args> void construct(_Up* __p, _Args&&... __args) { 
        ::new((void *)__p) _Up(std::forward<_Args>(__args)...); 
    }

    template<typename _Up> void destroy(_Up* __p) {
        __p->~_Up(); 
    }
    #else
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 402. wrong new expression in [some_] allocator::construct
    void construct(pointer __p, const _Tp& __val) { 
        ::new((void *)__p) _Tp(__val); 
    }

    void destroy(pointer __p) {
        __p->~_Tp(); 
    }
    #endif
};

下面将对部分重要成员进行详细说明

成员:rebind()

template<typename _Tp1>
struct rebind{ 
    typedef new_allocator<_Tp1> other; 
};

new_allocator是一个内存分配器,而rebind()的作用就是绑定不同数据类型的内存分配器。典型的就是list容器。

typedef typename _Alloc::template rebind<_List_node<_Tp> >::other _Node_alloc_type;
typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;

最简单的list包含一个 int data 和 struct node *next两个成员;当我们定义容器时将类型传给allocator,但就会导致allocator无法识别 struct node *next;

rebind()就是为了解决这种情况,它允许容器用一个分配器_Alloc再定义多个不同的分配器,以保证一致性。这样设置也使得new_allocator类的有参构造不被需要了

分别从vector和list的源码来看:

(1)vector

从调用先后依次为 c++/vector,c++/bits/stl_vector.h
class vector和它的基类 _Vector_base都在源文件<stl_vector.h>,文件只起到一个引入作用

template<typename _Tp, typename _Alloc>
struct _Vector_base{
    typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;
    typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer pointer;
    ...;
}

(2)list

从顶层到底层依次include的是 c++/list,c++/bits/stl_list.h,
class list和它的基类_List_base都在源文件<stl_list.h>之中

template<typename _Tp, typename _Alloc>
class _List_base{
protected:
	typedef typename _Alloc::template rebind<_List_node<_Tp> >::other _Node_alloc_type;
	typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;
    ...;
}
  • 可以看到,list和vector都执行了一次<_Tp>的rebind(),这相当于进行了有参构造。但list比vector多出了一次rebind(),这是因为list还需要一个节点内存分配器
  • rebind主要服务于stl链式容器。vector的内存分配器是为整个vector服务的,当vector容量不够需要扩充时,它将重新用分配器申请一个vector空间,再复制过去。以int为例,分配器只需要分配int*n
  • 而list不一样的是,同样定义int,但分配器不能给它分配int类型,因为每一个链表节点还包括前后指针,因此需要分配的是<_list_node>类型。rebind()可以使得 list 容器能像其他容器那样使用 listl 这样的便利方式

浅析:
list中还定义了一个list_node节点类,节点是一个<_ List _node< _Tp>>类型的数据结构,用 _Tp类型无法对其进行正确分配。
也可以为容器增加一个分配器参数,即两个分配器参数,但是为了确保两个分配器是一样的,更好还是使用rebind(),即避免 _Tp使用std::allocator分配器,而节点使用其他分配器如pool_allocator;(因为不同分配器分配空间大小可能不同)

rebind可以重新指定分配器分配空间的类型,实现了对不同类型使用同一种内存分配策略的要求。当容器需要对不同类型使用同一分配策略时,rebind的意义就体现出来。

成员:allocate()

配置内存空间,返回 __n (允许为0)个Tp类型大小的内存空间

pointer allocate(size_type __n, const void* = 0){
    if (__n > this->max_size())
        std::__throw_bad_alloc();
    return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
}
  • __n > this->max_size() 检查申请的大小是否超过当前进程最大可用内存,如果超过就抛出异常
  • operator new是实际分配空间的操作,封装了malloc
  • static_cast允许基本类型的转换和上行转换(即儿子指针转给父指针)
  • const void* = 0:(尚未查到意义)

成员:deallocate()

void deallocate(pointer __p, size_type){ 
    ::operator delete(__p); 
}

释放__p指向的内存空间

  • __p不允许为空指针
  • operator delete封装了free函数,用于释放内存
  • 这里的size_type并没有被使用,因为free()不需要大小参数,但是其他分配器如boost的pool_allocator就使用了,为保证stl调用的一致性,使用了这一个参数

成员:construct()

allocate分配内存,construct初始化内存

template<typename _Up, typename... _Args> void construct(_Up* __p, _Args&&... __args) { 
    ::new((void *)__p) _Up(std::forward<_Args>(__args)...); 
}

construct的使用例子

allocator<string> alloc;//allocator对象,类型为string
auto const p = alloc.allocate(3);//为3个string分配空间,但未初始化
auto q = p;//q指向第一个未初始化的string
alloc.construct(q++);//为string初始化,第1个为空字符串
alloc.construct(q++,5,'a');//第2个为"aaaaa"
alloc.construct(q++,"hi");//第3个为"hi"

construct涉及到转发forward,std::forward将输入的参数原封不动(指左右值)地传递到下一个函数中(new中),这里可以当作forward不存在

成员:destory()

template<typename _Up> void destroy(_Up* __p) {
    __p->~_Up(); 
}

调用_Up的析构函数,释放其空间

class allocator

  • 继承了new_allocator
  • 有关于分配的重载,但后面的几个函数暂时不是很清楚
#ifndef _ALLOCATOR_H
#define _ALLOCATOR_H 1

#include <bits/c++allocator.h> // Define the base class to std::allocator.
#include <bits/memoryfwd.h>
#if __cplusplus >= 201103L
#include <type_traits>
#endif

namespace std _GLIBCXX_VISIBILITY(default) {
_GLIBCXX_BEGIN_NAMESPACE_VERSION
    //没有继承父类的allocator
    template<>
    class allocator<void> {
        public:
        typedef size_t      size_type;
        typedef ptrdiff_t   difference_type;
        typedef void*       pointer;
        typedef const void* const_pointer;
        typedef void        value_type;
		
        //定义rebind
        template<typename _Tp1>
        struct rebind { 
            typedef allocator<_Tp1> other; 
        };

        #if __cplusplus >= 201103L
        // _GLIBCXX_RESOLVE_LIB_DEFECTS
        // 2103. std::allocator propagate_on_container_move_assignment
        typedef true_type propagate_on_container_move_assignment;
        #endif
    };
	
    //继承父类的allocator,__allocator_base实际上就是new_allocator
    template<typename _Tp>
    class allocator: public __allocator_base<_Tp> {
        public:
        typedef size_t     size_type;
        typedef ptrdiff_t  difference_type;
        typedef _Tp*       pointer;
        typedef const _Tp* const_pointer;
        typedef _Tp&       reference;
        typedef const _Tp& const_reference;
        typedef _Tp        value_type;
		
        //重写父类的rebind
        template<typename _Tp1>
        struct rebind { 
            typedef allocator<_Tp1> other; 
        };

        #if __cplusplus >= 201103L
        // _GLIBCXX_RESOLVE_LIB_DEFECTS
        // 2103. std::allocator propagate_on_container_move_assignment
        typedef true_type propagate_on_container_move_assignment;
        #endif
		
        //无参构造
        allocator() throw() { }
		
        //拷贝构造
        allocator(const allocator& __a) throw() : __allocator_base<_Tp>(__a) { }

        template<typename _Tp1> 
        allocator(const allocator<_Tp1>&) throw() { }

        ~allocator() throw() { }

        // Inherit everything else.
        // 从new_allocator那里继承allocate, deallocate, construct, destory等重要函数
    };
	
    //对==和!=的重载,这里的4个重载函数都表达的是:只要是用std::allocator实例化的对象,就一定相同,无论类型是什么
    template<typename _T1, typename _T2>
    inline bool operator==(const allocator<_T1>&, const allocator<_T2>&) _GLIBCXX_USE_NOEXCEPT { 
        return true; 
    }
    template<typename _Tp>
    inline bool operator==(const allocator<_Tp>&, const allocator<_Tp>&) _GLIBCXX_USE_NOEXCEPT { 
        return true; 
    }
    template<typename _T1, typename _T2>
    inline bool operator!=(const allocator<_T1>&, const allocator<_T2>&) _GLIBCXX_USE_NOEXCEPT { 
        return false; 
    }
    template<typename _Tp>
    inline bool operator!=(const allocator<_Tp>&, const allocator<_Tp>&) _GLIBCXX_USE_NOEXCEPT { 
        return false; 
    }

    /// @} group allocator

    // Inhibit implicit instantiations for required instantiations,
    // which are defined via explicit instantiations elsewhere.
    #if _GLIBCXX_EXTERN_TEMPLATE
    extern template class allocator<char>;
    extern template class allocator<wchar_t>;
    #endif

    // Undefine.
    #undef __allocator_base
    
    // To implement Option 3 of DR 431.
    template<typename _Alloc, bool = __is_empty(_Alloc)>
    struct __alloc_swap { 
        static void _S_do_it(_Alloc&, _Alloc&) _GLIBCXX_NOEXCEPT { } 
    };

    //如果分配器不一样,就进行交换(应用?)
    template<typename _Alloc>
    struct __alloc_swap<_Alloc, false> {
        static void _S_do_it(_Alloc& __one, _Alloc& __two) _GLIBCXX_NOEXCEPT {
            // Precondition: swappable allocators.
            if (__one != __two)
                swap(__one, __two);
        }
    };
	
    // neq表示不等的意思
    // Optimize for stateless allocators.
    template<typename _Alloc, bool = __is_empty(_Alloc)>
    struct __alloc_neq {
        static bool _S_do_it(const _Alloc&, const _Alloc&) { 
            return false; 
        }
    };
    template<typename _Alloc>
    struct __alloc_neq<_Alloc, false> {
        static bool _S_do_it(const _Alloc& __one, const _Alloc& __two) { 
            return __one != __two; 
        }
    };

    #if __cplusplus >= 201103L
    template<typename _Tp, bool = __or_<is_copy_constructible<typename _Tp::value_type>,
    is_nothrow_move_constructible<typename _Tp::value_type>>::value>
    struct __shrink_to_fit_aux { 
        static bool _S_do_it(_Tp&) noexcept { 
            return false; 
        } 
    };

    template<typename _Tp>
    struct __shrink_to_fit_aux<_Tp, true> {
        static bool _S_do_it(_Tp& __c) noexcept {
            #if __cpp_exceptions
            try {
                _Tp(__make_move_if_noexcept_iterator(__c.begin()),
                    __make_move_if_noexcept_iterator(__c.end()),
                    __c.get_allocator()).swap(__c);
                return true;
            } catch(...) { return false; }
            #else
            return false;
            #endif
        }
    };
    #endif

    _GLIBCXX_END_NAMESPACE_VERSION
} // namespace std

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值