STL源码剖析-配置器

时间:2019-09-23
本文章向大家介绍STL源码剖析-配置器,主要包括STL源码剖析-配置器使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

作用:对内存的管理

接口:申请和释放

内容:

  • 几个全局函数

  • 一级配置器

  • 二级配置器

准备知识

POD是什么:
  • Plain Old Data简称POD,表示传统的C语言类型;与POD类型对应的是非POD类型,表示C++独有的类型

  • 区别:POD类型可以直接新进行批量拷贝,非POD类型需要单个对象逐个拷贝,不能批量拷贝

  • 非POD类型不能直接拷贝的原因之一:

    • 如何类成员中包含引用或指针,如果直接拷贝,就会出现两个引用或指针指向同一块内存情况,很有可能直接导致内存多次释放和相互影响的危险

new做了什么:
class Foo { ... }

Foo *f = new Foo();
delete f;
  1. 调用全局::operator new函数申请内存

  2. 调用Foo:Foo()构造对象

delete做了什么:
  1. 调用全局Foo:~Foo()析构对象

  2. 调用::operator delete释放内存

STL中文件组织结构:
  • stl_construct.h:定义了全局的construct和destory函数

  • stl_alloc.h:定义了一二级配置器

  • stl_uninitialized.h:定义了一些全局函数,主要用于填充和复制大块内存数据

建构和解构基本工具:construct() 和 destroy()

第一版:
  • new (p) T1(value):替换构造函数,在p指向的内存中,使用value构造数据,即在p指向的内存中生成和value一样的数据

  • pointer->~T():调用T对象的析构函数,和一般的使用场景不一样,这里是显式调用析构函数

template <class T1, class T2>
inline void construct(T1* p, const T2& value) 
{
    new (p) T1(value); 
}

template <class T>
inline void destroy(T* pointer) 
{
    pointer->~T(); 
}
第二版:
  • 优势:可以更具POD类型进行区分,更安全,更搞笑

//就是一层封装
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) 
{
    __destroy(first, last, value_type(first));
}

//使用元编程技术,获取操作数据的类型,判断是POD类型还是非POD类型
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*)
{
    typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
    __destroy_aux(first, last, trivial_destructor());
}

//非POD类型,单个析构每一个对象
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type)
{
    for( ; first < last; ++first)
        destroy(&*first);
}

//POD类型无需析构
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) 
{
}

//下面两个函数是特化版本,只为提升效率
//char是8位,ANSI编码
//wchar_t是16位或32位,根据C或C++库而定,是Unicode编码
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

一级配置器

  • 设计两级配置器的原因是考虑到了小块内存的申请和释放会导致内存碎片的问题

  • 大于128直接就使用一级配置器,小于128直接就使用二级配置器

  • 一级配置器直接使用malloc和free操作堆内存

  • 一级配置器的类名__malloc_alloc_template

  • malloc(n):申请n字节的内存

  • realloc(p, new_n):在原来内存的基础上减少或是增加内存

    • 如果将分配的内存减少,realloc仅仅是改变索引的信息

    • 如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针

    • 如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置

    • 如果申请失败,将返回NULL,此时,原来的指针仍然有效

  • C++ new handler 机制:实现系统在内存配置需求无法被满足的情况下,调用事先设置的函数,对内存进行立即回收,尝试获取内存,获取成功就正常执行,获取失败就抛异常

class __malloc_alloc_template 
{
private:
    static void *oom_malloc(size_t); //内存不足的时候调用
    static void *oom_realloc(void *, size_t); //内存不足的时候调用
    static void (* __malloc_alloc_oom_handler)();
    
public:
    static void * allocate(size_t n)
    {
        void *result = malloc(n); 
        if (0 == result) 
            result = oom_malloc(n);//oom_malloc中会尝试获取内存,并分配内存
        return result;
    }
    
    static void deallocate(void *p, size_t)
    {
        free(p); 
    }
    
    static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
    {
        void * result = realloc(p, new_sz); 
        if (0 == result) 
            result = oom_realloc(p, new_sz);//oom_realloc中会尝试获取内存,并分配内存
        return result;
    }

    //设置回收内存的函数,并返回原来回收内存的函数
    static void (* set_malloc_handler(void (*f)()))()
    {
        void (* old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return(old);
    }
};

void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;

void * __malloc_alloc_template<inst>::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; //抛出alloc的异常,表示申请内存失败
        }
        
        (*my_malloc_handler)();
        result = malloc(n);
        
        // 再次尝试配置内存。
        if (result) 
            return(result);
    }
}

void * __malloc_alloc_template<inst>::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)();
        result = realloc(p, n);

        // 再次尝试配置内存。
        if (result) 
            return(result);
    }
}

二级配置器

  • 二级配置器的类名__default_alloc_template

  • 二级配置器只处理小于等于128字节的内存申请

  • 二级配置器为了方便管理小块内存,将所有的内存按照8的倍数大小进行管理,每一种大小维护一个链表,一共16个链表

  • 16个链表对应的大小:8 16 24 32 40 48 56 64 72 80 88 96 104 112 120 128

  • 使用一个16个大小的数组,统一管理这16个链表

  • 链表的节点设计如下:

union obj {
    union obj * free_list_link; //指向下一个节点
    char client_data[1]; //指向一块内存
};
  • 节点设计成union类型的好处是节约内存,节点没有被申请的时候,使用free_list_link进行管理,申请后使用client_data返回

  • 节点的大小是4个字节,维护的链表中最小的内存大小是8字节,因此不存在维护开销的问题

  • 理解:内存的大小固定是8的整数倍,管理时将一个内存块的前面几个字节装换成union obj进行管理,申请时,直接返回整个内存块,相当于没有使用额外的内存管理链表,不存在开销

  • 内存的申请和释放就是节点从链表中拿出和放回的过程,对链表的操作是头插和头取

  • 二级配置器的声明如下:

enum {__ALIGN = 8}; // 对齐倍数是8
enum {__MAX_BYTES = 128}; //一二级配置器的分界点
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // free-lists的个数

class __default_alloc_template 
{
private:
    //将bytes调整从8的倍数,向上调整
    static size_t ROUND_UP(size_t bytes) 
    {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
    }
    
private:
    // free-lists 的节点构造
    union obj 
    { 
        union obj * free_list_link;
        char client_data[1];
    };
    
private:
    // 16个free-lists
    static obj * volatile free_list[__NFREELISTS];

    // 根据申请的内存大小,获取数组下标,确定从哪个链表中获取内存
    // n从1算起
    static size_t FREELIST_INDEX(size_t bytes) 
    {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }
    
    // 从内存池中获取一块内存,调用chunk_alloc,并插入到某一个链表中
    static void *refill(size_t n);
   
    // 从内存池中获取内存
    static char *chunk_alloc(size_t size, int &nobjs);
    

    static char *start_free; // 内存池起始地址
    static char *end_free;  // 内存池结束地址
    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){ /* 详述于后 */ }
};

// 以下是静态变量的定义
char *__default_alloc_template<threads, inst>::start_free = 0;

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

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

__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()
static void * allocate(size_t n)
{
    obj * volatile * my_free_list;
    obj * result;
    
    // 大于128调用第一级配置器
    if (n > (size_t) __MAX_BYTES)
    {
        return(malloc_alloc::allocate(n));
    }
    
    // 根据申请的内存大小,找出数组中的某一个列表
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if (result == 0) // 找到的链表为空,就到内存池中获取
    {
        void *r = refill(ROUND_UP(n)); //到内存池中获取内存
        return r;
    }
 
    //将链表的头结点取出后,将下一个节点放入数组中,恢复链表
    *my_free_list = result -> free_list_link;
    return (result);
};
  • 空间释放函数 deallocate()
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * volatile * my_free_list;
    
    // 大于128字节直接调用一级配置器
    if (n > (size_t) __MAX_BYTES)
    {
        malloc_alloc::deallocate(p, n);
        return;
    }

    // 根据释放的内存大小,找出数组中的某一个列表
    my_free_list = free_list + FREELIST_INDEX(n);
    // 将释放的内存头插入链表
    q -> free_list_link = *my_free_list;
    // 将头结点放入数组
    *my_free_list = q;
}
  • 重新充填 free lists
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;

    // 
    char * chunk = chunk_alloc(n, nobjs); 
    obj * volatile * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    // 如果从内存池中只获取到了一个节点的内存,那就直接返回
    if (1 == nobjs) 
        return(chunk);
    
    // 找到链表
    my_free_list = free_list + FREELIST_INDEX(n);

    result = (obj *)chunk;
    
    // 将首个链表插入数组
    *my_free_list = next_obj = (obj *)(chunk + n);
    // 构建链表
    for (i = 1; ; i++) 
    { 
        current_obj = next_obj;
        next_obj = (obj *)((char *)next_obj + n);
        if (nobjs - 1 == i)
        {
            current_obj -> free_list_link = 0;
            break;
        } 
        else
        {
            current_obj -> free_list_link = next_obj;
        }
    }
    return(result);
}
  • chunk_alloc()
    • 说明:函数中所有的递归调用都是为了修正nobjs的大小,因为上一次的调用只是将堆内存放入内存池中,并没有分配内存返回,所以递归调用返回所申请的内存

chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free; 
    
    if (bytes_left >= total_bytes) 
    {
        // 内存池中有足够的内存,直接取出返回
        result = start_free;
        start_free += total_bytes;
    } 
    else if (bytes_left >= size) 
    {
        // 内存池中的内存大于一个节点的需要的内存大小,有多少返回多少
        nobjs = bytes_left/size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return(result);
    } 
    else 
    {
        // 池子中的内存都不够一个节点所需要的大小,直接向堆空间申请
        // 每次申请的大小是2倍实际申请大小,加上一个越来越带的随机值(8的倍数)
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
        if (bytes_left > 0) 
        {
               // 将内存池中剩余的内存插入链表,不浪费一点内存
            obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }
        
        // 直接向堆空间申请
        start_free = (char *)malloc(bytes_to_get);
        // 申请失败就在链表数组中,需要内存大的数组,找出一块内存返回
        if (0 == start_free) 
        {
            int i;
            obj * volatile * my_free_list, *p;

            for (i = size; i <= __MAX_BYTES; i += __ALIGN) 
            {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if (0 != p) 
                {
                    *my_free_list = p -> free_list_link;
                    start_free = (char *)p;
                    end_free = start_free + i;
                    return(chunk_alloc(size, nobjs));
                }
            }
            // 数组中所有的链表都没有内存了,山穷水尽了,直接向一级配置器申请,可能抛异常
            end_free = 0;
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return(chunk_alloc(size, nobjs));
    }
}

几个全局函数

uninitialized_copy函数
  • 拷贝一块区域内的数据到另一块区域,源:firstlast,目的:resultresult+(last-first)

  • 根据POD数据和非POD数据进行区分,POD数据直接拷贝,非POD数据一个个调用替换构造函数拷贝

  • charwchar单独处理,使用函数特化技术,效率更高

  • 难点解析:

typedef typename __type_traits<T>::is_POD_type is_POD;

typename __type_traits<T>::is_POD_type //提取出类型T中is_POD_type的类型

is_POD() // 构造一个临时的is_POD对象,便于调用重载函数
    
// STL中,默认将所有类型都设置为非POD对象,即属于类型__false_type类的成员
    
// 例子:
    struct __false_type; // 定义类型
    struct __true_type;

    // 数据类型中定义is_POD_type,后续就可以使用了
    typedef  __false_type is_POD_type;
  • 源码:
// 对外统一接口
template <class InputIterator, class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result)
{
    return __uninitialized_copy(first, last, result, value_type(result));
}

// 提取数据的类型,分别调用处理POD数据的函数和非POD数据的函数
template <class InputIterator, class ForwardIterator, class T>
inline ForwardIterator __uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result, T*)
{
    typedef typename __type_traits<T>::is_POD_type is_POD;
    return __uninitialized_copy_aux(first, last, result, is_POD());
}

// POD数据直接拷贝
template <class InputIterator, class ForwardIterator>
inline ForwardIterator __uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, __true_type) 
{
    return copy(first, last, result); // 后续讲解,算法章节
}

//非POD数据单独掉调用替换构造函数拷贝,循环处理
template <class InputIterator, class ForwardIterator>
ForwardIterator __uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, __false_type) 
{
    ForwardIterator cur = result;

    for ( ; first != last; ++first, ++cur)
        construct(&*cur, *first); 
    return cur;
}
// 特化函数
template<>
inline char* uninitialized_copy(const char* first, const char* last, char* result) 
{
    memmove(result, first, last - first); // 处理字符串的效率非常高
    return result + (last - first);
}

// 特化函数
template<>
inline wchar_t* uninitialized_copy(const wchar_t* first, const wchar_t* last, wchar_t* result) 
{
    memmove(result, first, sizeof(wchar_t) * (last - first));
    return result + (last - first);
}
uninitialized_fill函数
  • 填充一块内存中的内容为指定数据,内存为:firstlast,数据为:`const T & x

  • 类似uninitialized_copy也区分POD类型和非POD类型

  • 源码:

// 对外统一接口
template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x)
{
    __uninitialized_fill(first, last, x, value_type(first));
}

// 提取数据的类型,分别调用处理POD数据的函数和非POD数据的函数
template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x, T1*) 
{
    typedef typename __type_traits<T1>::is_POD_type is_POD;
    __uninitialized_fill_aux(first, last, x, is_POD());
}

// POD数据直接拷贝
template <class ForwardIterator, class T>
inline void __uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, __true_type)
{
    fill(first, last, x); // 后续讲解,算法章节
}

//非POD数据单独掉调用替换构造函数拷贝,循环处理
template <class ForwardIterator, class T>
void __uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, __false_type)
{
    ForwardIterator cur = first;

    for ( ; cur != last; ++cur)
        construct(&*cur, x); 
}
uninitialized_fill_n函数
  • 和uninitialized_fill函数功能基本相同,只是入参有区别

  • 数据区:firstfirst+n

  • 类似uninitialized_copy也区分POD类型和非POD类型

// 对外统一接口
template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x) 
{
    return __uninitialized_fill_n(first, n, x, value_type(first));
}

// 提取数据的类型,分别调用处理POD数据的函数和非POD数据的函数
template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n, const T&x, T1*)
{
    typedef typename __type_traits<T1>::is_POD_type is_POD;
    return __uninitialized_fill_n_aux(first, n, x, is_POD());
}

// POD数据直接拷贝
template <class ForwardIterator, class Size, class T>
inline ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, __true_type)
{
    return fill_n(first, n, x); // 后续讲解,算法章节
}

//非POD数据单独掉调用替换构造函数拷贝,循环处理
template <class ForwardIterator, class Size, class T>
ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, __false_type) 
{
    ForwardIterator cur = first;

    for ( ; n > 0; --n, ++cur)
        construct(&*cur, x); 
    return cur;
}

原文地址:https://www.cnblogs.com/chusiyong/p/11572542.html