2.空间配置器解析

0. 介绍

在STL六大组件中,用户通常接触不到空间配置器allocator,其总是隐藏在其他组件的背后。但是,从STL实现的角度,第一个需要介绍的就是空间配置器,因为所有存放在容器container中的数据都需要配置空间,从而进行高效的增删改查等操作。
参考链接:

1. 空间配置器

一般而言,在C++中进行内存空间分配和释放操作,通常使用如下类似代码:

class Foo {...};
Foo* pf = new Foo; // 配置内存,然后构造对象
delete pf;			// 析构对象,然后释放内存

其中new算式包含两个阶段操作:

  • 调用::operator new 配置内存;

::operator new 分为三种形式:

  • void* operator new (std::size_t size) throw (std::bad_alloc); // 分配size内存大小,分配失败则抛出异常
  • void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw(); //分配size内存大小,分配失败则返回NULL
  • void* operator new (std::size_t size, void* ptr) throw(); // placement new,本质是对::operator new重载。它不分配内存,调用合适的构造函数在ptr所指的地方构造一个对象,之后返回实参指针ptr。

三种形式使用分别如下:

  • A* a = new A; //底层调用第一种
  • A* a = new(std::nothrow) A; //底层调用第二种
  • new (p)A(); //底层调用第三种 ,在指针p上调用A::A()构造函数
  • 调用Foo::Foo() 构造对象;

同时new算式还具有如下的特点:

  • 通过_set_new_handler设置new配置内存失败调用的回调函数;
  • new配置内存失败后,不会像malloc一样返回NULL,而是抛出异常,程序员判断内存是否配置成功需要使用异常捕获的机制;
try
{
    int* p = new int[1000000000000];
}
catch (std::bad_alloc)
{
    // 内存配置失败处理
}
  • 通过使用nothrow可以设置new配置内存失败后不抛出异常,而是返回NULL;
int* p = new(std::nothrow) int[1000000000000];
if(p == nullptr)
{
    // 内存配置失败处理
}

其中delete算式也包含两阶段操作:

  • 调用Foo::~Foo()将对象析构
  • 调用::operator delete()释放内存

1.1 SGI标准的空间配置器allocator

SGI中的allocator配置器,只是简单将C++中的::operator new 和::operator delete做了一层封装,效率不佳,因此通常不作为默认空间配置器使用。

#include <new>
#include <cstddef> // for ptrdiff_t, size_t
#include <cstdlib>  // for exit()
#include <climits>      // for UINT_MAX
#include <iostream>    // for cerr

namespace sample
{
template <class T>
inline T* _allocate(ptrdiff_t size, T*)
{
    std::set_new_handler(0);
    T* tmp = (T*) (::operator new((size_t)(size * sizeof(T))));
    if(tmp == 0)
    {
        std::cerr << "*out of memory" << std::endl;
        exit(1);
    }
    return tmp;
}

template <class T>
inline void _deallocate(T* buffer)
{
    ::operator delete(buffer);
}

template <class T1, class T2>
inline void _construct(T1* p, const T2& value)
{
    new(p) T1(value);   // placement new, 调用T1::T1(value)构造函数
}

template <class T>
inline void _destroy(T* ptr)
{
    ptr->~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;

    pointer allocate(size_type n, const void* hint=0)
    {
        return _allocate((difference_type)n, (pointer)0);
    }

    void deallocate(pointer p, size_type n)
    {
        _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 const_address(const_reference x)
    {
        return (const_pointer)&x;
    }

    size_type max_size() const{
        return size_type(UINT_MAX / sizeof(T));
    }
};      
}       // end of namespace sample

int main()
{
    const int MAX_NUMS = 10;
    sample::allocator<int> alloc;
    int* header = alloc.allocate(MAX_NUMS);
    for (int i=0; i<MAX_NUMS; ++i)
    {
        alloc.construct(header+i, i);
    }
    for(int i=0; i<MAX_NUMS; ++i)
    {
        std::cout << *(header + i) << "\t";
    }
    std::cout << std::endl;

    alloc.destroy(header);
    alloc.deallocate(header, MAX_NUMS);
    return 0;
}

1.2 具有次配置力的SGI空间配置器alloc

上述的空间配置器allocator只是对::operator new::operator delete简单包装,并没有任何效率上的强化。SGI特殊的空间配置器alloc,在内存配置、对象构建等方面上做了很多优化,是STL容器默认的空间配置器。
alloc中将newdelete的两阶段行为分离,内存配置操作由alloc::allocate()负责;内存释放操作由alloc::deallocate()负责;对象构造操作由::construct()负责;对象析构操作由::destroy()负责。
内存空间的配置与释放由stl_alloc.h文件负责,对象内容的构造与析构由stl_construct.h文件负责。

在这里插入图片描述

对象内容构造与析构

stl_constuct.h文件中,construct()用于对象构造,destroy()用于对象析构,整体流程如下图:

在这里插入图片描述

  • construct()接收一个指针p和一个初值value,该函数的功能是在指针p所指的空间上调用对象构造函数,并用value值初始化,构造对象;
  • destroy()函数的功能是析构对象,分为两种情况:
    • 第一种情况接收一个指针,通过直接调用对象的析构函数,析构指针所指向的对象;
    • 第二种情况接收firstlast两个迭代器,功能是析构[first,last)范围内的所有对象。对于范围内的每个对象,需要通过value_type()获取迭代器所指对象的类别,在利用type_traits判断该类别的析构函数是否trivial,若是(__true_type),则什么都不做结束,若是(__false_type),则遍历范围内的每个对象进行析构。
      :::warning
      trivial的意思是琐碎的,即不重要的,若经过type_traits之后返回__true_type,则表示数据类型不重要,不需要调用析构函数,类似int、double等基本数据类型,返回__false_type,则表示数据类型对象必须调用析构函数,析构对象,例如成员变量中有指针之类的,涉及动态内存配置和释放。
      :::

空间的配置与释放

空间配置器alloc使用malloc()free()完成内存的配置和释放,同时,考虑到小型区块所可能造成的内存破碎问题,alloc有两种形式。
当配置内存块的大小超过128 bytes时,调用第一级配置器__malloc_alloc_template,当配置区块小于128 bytes时,调用第二级配置器__default_alloc_template
SGI中,第一级或第二级配置器具体使用时都被定义为alloc,同时,SGIalloc又进行了再次包装,接口为simple_alloc
容器、allocsimple_alloc三者之间的关系如下,alloc可能是第一级或者第二级配置器,作为模板参数传入到容器中,再传入到simple_alloc中,得到专属空间配置器data_allocator

在这里插入图片描述

第一级配置器__malloc_alloc_template
template <int __inst>
class __malloc_alloc_template {

private:
  // 以下函数用于处理内存不足的情况
  // oom : out of memory.
  static void* _S_oom_malloc(size_t);
  static void* _S_oom_realloc(void*, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
  static void (* __malloc_alloc_oom_handler)();
#endif

public:

  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);
    // 内存分配失败, 改用oom_malloc()
    if (0 == __result) __result = _S_oom_malloc(__n);
    return __result;
  }

  static void deallocate(void* __p, size_t /* __n */)
  {
    free(__p);
  }

  static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
  {
    void* __result = realloc(__p, __new_sz);
     // 内存分配失败, 改用oom_realloc()
    if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
    return __result;
  }
  // 模拟C++的set_new_handler()
  static void (* __set_malloc_handler(void (*__f)()))()
  {
    void (* __old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = __f;
    return(__old);
  }

};

第一级配置器使用malloc(), free(), realloc()C函数执行实际的内存配置、释放、重配置操作,并实现类似C++ new-handler的机制。

  • alloc定义为第一级配置器
// inst参数被指定为0
typedef __malloc_alloc_template<0> malloc_alloc; 
typedef malloc_alloc alloc;
  • C++ new handler机制

new handler机制是,当内存配置分配无法满足要求时,会调用一个用户执行的函数。也就是,在使用new配置内存时,一旦::operator new无法满足配置要求,则在丢出std::bad_alloc异常之前,会先调用用户指定的处理例程,该例程被称为new-handler

/** If you write your own error handler to be called by @c new, it must
*  be of this type.  */
typedef void (*new_handler)();	// 函数指针

/// Takes a replacement handler as the argument, returns the
/// previous handler.
new_handler set_new_handler(new_handler) throw();

通过调用set_new_handler即可设置new-handlerset_new_handler 的形参是一个指向函数的指针,返回值是也是一个指向函数的指针,这个函数是 set_new_handler 被调用前有效的new-handler

#include <new> // new
#include <iostream> // cerr
#include <climits>  // ULONG_MAX

// function to call if operator new can't allocate enough memory
void outOfMem()
{
 std::cerr << "Unable to satisfy request for memory\n";
 std::abort();
}

int main()
{
 std::set_new_handler(outOfMem);
 size_t memory_size = 1000000000000L;
 std::cout << "want to allocate " << memory_size * sizeof(int) << " bytes." << std::endl;
 try
 {
    int *pBigDataArray = new int[memory_size];
 }
 catch(const std::exception& e)
 {
    std::cerr << e.what() << '\n';
 }
}
/* Output:
want to allocate 4000000000000 bytes.
Unable to satisfy request for memory
Aborted
*/

默认情况下第一级配置器的new_handler为0。

// malloc_alloc out-of-memory handling

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif
  • oom_malloc()实现
template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
    void (* __my_malloc_handler)();
    void* __result;

    for (;;) {      // 不断尝试、配置、再释放、再配置
        __my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*__my_malloc_handler)();   // 调用new_handler, 企图释放内存
        __result = malloc(__n);     // 再次尝试配置内存
        if (__result) return(__result);
    }
}
  • oom_realloc()实现
template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
    void (* __my_malloc_handler)();
    void* __result;

    for (;;) {      // 不断尝试、配置、再释放、再配置
        __my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*__my_malloc_handler)();// 调用new_handler, 企图释放内存
        __result = realloc(__p, __n);// 再次尝试配置内存
        if (__result) return(__result);
    }
}

当第一级配置器调用malloc()realloc()不成功后,会调用oom_malloc()oom_realloc()。后两者内部都包含for循环,不断调用new_hanlder处理例程,期望在某次调用之后获得足够的内存而圆满完成任务。
但是如果用户没有设置new_handler处理例程,则会调用__THROW_BAD_ALLOC丢出bad_alloc异常信息。

第二级配置器__default_alloc_template

当配置一块内存时,需要额外空间管理内存,如下图所示。为避免太多小额区块分配造成内存的碎片,以及配置时额外空间负担,第二级配置器提供一些额外机制解决该问题。

在这里插入图片描述


针对配置内存区块小于128 bytes情况,第二级配置器采用内存池(memory pool)方式管理。具体做法如下:

  1. 配置一大块内存(8的倍数),并使用自由链表free-list对其进行维护;
  2. 当相同大小的内存需求时,直接从free-list中拨出,对于任何小额区块的内存需求量都会上调到8的倍数;
  3. 如果客端释放小额区块,配置器会将其回收到free-list中;
  4. 维护了16个free-lists,分别管理8、16、… 128bytes的小额区块。

free-list节点结构如下:

  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };

在这里插入图片描述

部分实现代码

enum {__ALIGN = 8};     // 小额区块的上调边界
enum {__MAX_BYTES = 128};   // 小额区块的上限
enum {__NFREELISTS = __MAX_BYTES / __ALIGN}; // free-lists的个数

template <bool threads, int inst>
class __default_alloc_template {
private:
    // ROUND_UP将小额区块上到到8的倍数
    static size_t ROUND_UP(size_t bytes)
    {
        return (((bytes) + __ALIGN -1) & ~(__ALIGN - 1));
    }
    union obj{  // free-list节点
        union obj* free_list_link;
        char client_data[1];
    };
    // 16个free-list
    static obj* volatile free_list[__NFREELISTS];
    // 根据分别区块的大小, 决定使用第n号free-list, n从0开始
    static size_t FREELIST_INDEX(size_t bytes){
        return (((bytes) + __ALIGN-1) / __ALIGN -1);
    }
    // 返回一个大小为n的对象,并可能将其他分配大小为n的区块加入到free-list中
    static void* refill(size_t n);
    // 配置一大块空间,空间大小为size * nobjs
    static char* chunk_alloc(size_t size, int& nobjs);
    // chunk allocation state
    static char* start_free;        // memory pool起始位置
    static char* end_free;          // memory pool结束位置
    static size_t heap_size;
public:
    static void* allocate(size_t n);
    static void deallocate(void* p, size_t n);
    static void* reallocate(void* p, size_t old_sz, size_t new_sz);
};
// static data member的初始化
template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = 0;

template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = 0;

template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj* volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

空间配置函数allocate

如下图所示,allocate函数首先会判断区块的大小,大于128 bytes则会转而调用第一级配置器,小于128 bytes就检查对应的free list。如果 free list之内有可用的区块,就直接拿来用,如果没有可用区块,就调用refill准备为free list重新填充空间。

image.png

free-list中直接调出内存区块的操作步骤如下:

  • 根据配置的内存区块大小,找到合适的free-list节点;
  • 将节点的指向赋值给临时的result
  • 将节点指向位置调整到result指向的下一个节点;
    https://img-blog.csdnimg.cn/img_convert/e0c4a3255975d1a26f727c1fcf44b8a0.png#averageHue=#f8f8f8&clientId=u3ffa05ab-3976-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u1bbe1db3&margin=[object Object]&name=image.png&originHeight=396&originWidth=587&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80396&status=done&style=none&taskId=ucfdaefe5-5573-4716-93f8-b2f3cd1c4d4&title=&width=587
空间释放函数deallocate

deallocate首先根据区块大小,决定是否调用第一个配置器,如果区块小于128 bytes就找到对应的free-list,将其回收。

在这里插入图片描述

区块回收纳入free-list的操作如下图所示:

在这里插入图片描述

重新填充free-list函数refill

free-list中没有可用区块时,就会调用refill(),重新配置一大片内存,并将其连接到free-list上。默认情况下,分配20个新区块、如果内存池空间不足,则区块个数可能小于20。

在这里插入图片描述

重新填充free-list的过程如下:

在这里插入图片描述

内存池(memory pool)

refill()中,通过chunk_alloc()从内存池中取空间给free-list使用,这一部分比较细节性的讨论了各种情况,在这里不做介绍。

2. 内存基本处理工具

STL中定义了五个全局函数,作用与未初始化空间上,功能分别如下:

  • construct()用于对象构造;
  • destroy()用于对象析构;
  • uninitialized_copy():对应copy()函数;
  • uninitialized_fill():对应fill()函数;
  • uninitialized_fill_n():对应fill_n()函数;

后面3个全局函数实际定义于stl_uninitialzed文件。

  1. uninitialized_fill_n()

该函数接收三个参数:

  • 迭代器first指向欲初始化空间的起始处;
  • n表示欲初始化空间的大小;
  • x表示初值;

函数运行逻辑如下图所示:

  • 利用type_traits萃取迭代器firstvalue type
  • 判断该型别是否为PODPOD(Plain Old Data)指的是标量型别或者C struct型别。POD型别其ctor/dtor/copy/assignment函数是不重要的,可以直接初始化,而对于non-POD则需要调用范围内每个对象的构造函数进行初始化。
    在这里插入图片描述

uninitialized_fill()函数的实现过程与之类似,这里不做分析了。

  1. uninitialized_copy()

该函数接收三个参数:

  • 迭代器first指向输入端的起始位置;
  • 迭代器last指向输入端的结束位置;
  • 迭代器result指向输出端(欲初始化空间)的起始处

函数逻辑与之前的uninitialized_fill_n()类似,先萃取迭代器的value type,判断是否为POD类别,分别调用不同的函数完成区间范围内对象的拷贝。

在这里插入图片描述

针对char*wchar_t*两种型别,涉及了特化版本,使用memmove来执行拷贝行为。

在这里插入图片描述

3. 总结

本文介绍了SGI STL中实现的空间配置器,包含标准的allocator和特殊的alloc,重点对空间配置器alloc进行了分析,分为对象构造、析构和空间配置、释放两大部分。在空间配置与释放章节,分别对第一级和第二级配置器的原理进行了细致分析,了解了各种情况下空间是如何配置和释放。最后,介绍了3个用于对象初始化的全局函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于微信小程序的家政服务预约系统采用PHP语言和微信小程序技术,数据库采用Mysql,运行软件为微信开发者工具。本系统实现了管理员和客户、员工三个角色的功能。管理员的功能为客户管理、员工管理、家政服务管理、服务预约管理、员工风采管理、客户需求管理、接单管理等。客户的功能为查看家政服务进行预约和发布自己的需求以及管理预约信息和接单信息等。员工可以查看预约信息和进行接单。本系统实现了网上预约家政服务的流程化管理,可以帮助工作人员的管理工作和帮助客户查询家政服务的相关信息,改变了客户找家政服务的方式,提高了预约家政服务的效率。 本系统是针对网上预约家政服务开发的工作管理系统,包括到所有的工作内容。可以使网上预约家政服务的工作合理化和流程化。本系统包括手机端设计和电脑端设计,有界面和数据库。本系统的使用角色分为管理员和客户、员工三个身份。管理员可以管理系统里的所有信息。员工可以发布服务信息和查询客户的需求进行接单。客户可以发布需求和预约家政服务以及管理预约信息、接单信息。 本功能可以实现家政服务信息的查询和删除,管理员添加家政服务信息功能填写正确的信息就可以实现家政服务信息的添加,点击家政服务信息管理功能可以看到基于微信小程序的家政服务预约系统里所有家政服务的信息,在添加家政服务信息的界面里需要填写标题信息,当信息填写不正确就会造成家政服务信息添加失败。员工风采信息可以使客户更好的了解员工。员工风采信息管理的流程为,管理员点击员工风采信息管理功能,查看员工风采信息,点击员工风采信息添加功能,输入员工风采信息然后点击提交按钮就可以完成员工风采信息的添加。客户需求信息关系着客户的家政服务预约,管理员可以查询和修改客户需求信息,还可以查看客户需求的添加时间。接单信息属于本系统里的核心数据,管理员可以对接单的信息进行查询。本功能设计的目的可以使家政服务进行及时的安排。管理员可以查询员工信息,可以进行修改删除。 客户可以查看自己的预约和修改自己的资料并发布需求以及管理接单信息等。 在首页里可以看到管理员添加和管理的信息,客户可以在首页里进行家政服务的预约和公司介绍信息的了解。 员工可以查询客户需求进行接单以及管理家政服务信息和留言信息、收藏信息等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值