《STL源码分析》学习笔记 — 空间配置器 — pool_allocator

本文详细分析了STL中空间配置器__pool_alloc_base及其派生类__pool_alloc,探讨了内存管理、内存优化策略。内容包括:内存池的工作原理,如_M_get_free_list、_M_round_up等函数实现,以及与new_allocator的性能对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


__pool_alloc 对应了书中所说的第一级配置器,其基类则对应第二级配置器。该配置器的工作目标是 减少小额区块造成的内存碎片。我们多次申请内存时,其在堆空间中的分配并不是连续的。因此,如果我们在堆中申请了许多小块的内存,就会造成当我们想要分配大内存时,系统找不到连续的内存以满足我们需要的空间。
我们这里使用的 cygwin 只提供了 gcc 的头文件。我们这里的函数实现有些是位于源文件中的。因此,我们将会使用 gcc10.2.0 的源码分析。

一、__pool_alloc_base

该基类定义了满足小额区块的内存上限(128bytes)。如果超过此上限,则交由派生类进行内存管理。当内存块大小不超过此上限时,则在内存池中进行内存管理(memory pool):

/**
*  @brief  Base class for __pool_alloc.
*
*  重要的实现性质:
*  0. 如果全局授权,则通过new分配内存
*  1. 如果用户要求的内存大于_S_max_bytes,直接从堆中分配内存
*  2. 在所有其它情况中,我们分配对象的大小为_S_round_up(requested_size)
*     这样用户持有足够的大小信息,使得我们可以将用户不再使用的对象防止到适当的 free-list中,而不会永远丢失该块内存
*/
class __pool_alloc_base
{
   
   
    typedef std::size_t size_t;
protected:

    enum {
   
    _S_align = 8 }; // 对齐方式
    enum {
   
    _S_max_bytes = 128 }; // 小额区块的内存上限
    enum {
   
    _S_free_list_size = (size_t)_S_max_bytes / (size_t)_S_align }; // 维护的区块链表数量

    union _Obj
    {
   
   
        union _Obj* _M_free_list_link;    // 下一个链表节点
        char        _M_client_data[1];    // 用户看到的内存地址
    };

    static _Obj* volatile         _S_free_list[_S_free_list_size]; // 自由链表数组

    // 维护内存块状态
    static char*                  _S_start_free; // 内存池中可用空间的起始地址
    static char*                  _S_end_free; // 内存池中可用空间的结束地址
    static size_t                 _S_heap_size; // 已经从堆中申请的总空间大小

    size_t _M_round_up(size_t __bytes)
    {
   
    return ((__bytes + (size_t)_S_align - 1) & ~((size_t)_S_align - 1)); }

    _GLIBCXX_CONST _Obj* volatile* _M_get_free_list(size_t __bytes) throw ();

    __mutex& _M_get_mutex() throw ();

    void* _M_refill(size_t __n);

    char* _M_allocate_chunk(size_t __n, int& __nobjs);
};

1、_M_get_free_list

这个接口是用户获取指定 free-list 的接口。

// Definitions for __pool_alloc_base.
__pool_alloc_base::_Obj* volatile* __pool_alloc_base::_M_get_free_list(size_t __bytes) throw ()
{
   
   
    size_t __i = ((__bytes + (size_t)_S_align - 1) / (size_t)_S_align - 1);
    return _S_free_list + __i;
}

这里用到了我们在头文件中声明的对齐方式属性 _S_align。不难看出,这里计算得到的 i 实际上是 bytes 除以 _S_align 后向上取整并-1。这里-1是因为 i 作为数组下标使用。那么返回的值就是 _S_free_list 的第 i+1 个元素地址。

看到这里,我们就明白 _S_free_list 中实际上保存了指向 _S_align 不同倍数大小的 free-list 指针。其第一个元素保存的是大小为0-8的那些内存块,第二个是9-16,依此类推。_S_free_list 的类型是 union _Obj*。从其定义,不难看出它实际上是维护了数个链表。因此,我们可以得出一个结论:对于 _S_align 某个整数倍范围内(如25-32)的内存,如果内存池中有相应空间可分配,那么 _S_free_list 中对应下标的链表中的首元素一定是处于未使用状态的

2、_M_round_up

此函数用于将某个数量扩展为8字节对其版本。

3、_M_allocate_chunk

// 从大的内存块(内存池)中(_S_start_free-_S_end_free)分配空间以避免碎片化。前提是充分利用空间
// __n 表示对象个数, __nobjs 表示每个对象大小(引用传递,以供派生类知道实际构造的对象个数)。
// 假设 __n 是适当对齐的,即为_S_align的整数倍。
// 这个函数要求调用者持有锁
char* __pool_alloc_base::_M_allocate_chunk(size_t __n, int& __nobjs)
{
   
   
    char* __result; // 返回值
    size_t __total_bytes = __n * __nobjs; // 需要的空间
    size_t __bytes_left = _S_end_free - _S_start_free; // 当前空闲块中剩余的空间

    if (__bytes_left >= __total_bytes)
    {
   
   
    	// 如果当前空闲块中剩余的空间,分配并更新空闲块的起始地址
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return __result ;
    }
    else if (__bytes_left >= __n)
    {
   
   
    	// 如果当前空闲块中剩余的空间不满足空间要求,但是至少能满足分配给一个对象
    	// 计算能分配给几个对象及需要的空间,分配并返回
        __nobjs = (int)(__bytes_left / __n);
        __total_bytes = __n * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return __result;
    }
    else
    {
   
   
        if (__bytes_left > 0)
        {
   
   
        	// 充分利用剩余的空闲块,将剩余的空间放到_S_free_list数组对应的链表中
        	// 从这里我们能看出来,这要求内存池中剩余的大小也是 _S_align 的整数倍,否则不能直接插入到某个内存池链表中
        	// 实现这个要求的方式就是:初始分配的内存时 _S_align 的整数倍,每次取对象时,其大小也
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值