作用:对内存的管理

接口:申请和释放

内容:

  • 几个全局函数

  • 一级配置器

  • 二级配置器

准备知识

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 ( == 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 ( == 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)() = ; 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 ( == 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 ( == 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[]; //指向一块内存
};
  • 节点设计成union类型的好处是节约内存,节点没有被申请的时候,使用free_list_link进行管理,申请后使用client_data返回

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

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

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

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

enum {__ALIGN = }; // 对齐倍数是8
enum {__MAX_BYTES = }; //一二级配置器的分界点
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-) & ~(__ALIGN - ));
} private:
// free-lists 的节点构造
union obj
{
union obj * free_list_link;
char client_data[];
}; private:
// 16个free-lists
static obj * volatile free_list[__NFREELISTS]; // 根据申请的内存大小,获取数组下标,确定从哪个链表中获取内存
// n从1算起
static size_t FREELIST_INDEX(size_t bytes)
{
return (((bytes) + __ALIGN-)/__ALIGN - );
} // 从内存池中获取一块内存,调用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 = ; char *__default_alloc_template<threads, inst>::end_free = ; size_t __default_alloc_template<threads, inst>::heap_size = ; __default_alloc_template<threads, inst>::obj * volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {, , , , , , , , , , , , , , , , };
  • 空间配置函式 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 == ) // 找到的链表为空,就到内存池中获取
{
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 = ; //
char * chunk = chunk_alloc(n, nobjs);
obj * volatile * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i; // 如果从内存池中只获取到了一个节点的内存,那就直接返回
if ( == nobjs)
return(chunk); // 找到链表
my_free_list = free_list + FREELIST_INDEX(n); result = (obj *)chunk; // 将首个链表插入数组
*my_free_list = next_obj = (obj *)(chunk + n);
// 构建链表
for (i = ; ; i++)
{
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + n);
if (nobjs - == i)
{
current_obj -> free_list_link = ;
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 = * total_bytes + ROUND_UP(heap_size >> );
if (bytes_left > )
{
// 将内存池中剩余的内存插入链表,不浪费一点内存
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 ( == 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 ( != p)
{
*my_free_list = p -> free_list_link;
start_free = (char *)p;
end_free = start_free + i;
return(chunk_alloc(size, nobjs));
}
}
// 数组中所有的链表都没有内存了,山穷水尽了,直接向一级配置器申请,可能抛异常
end_free = ;
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 > ; --n, ++cur)
construct(&*cur, x);
return cur;
}

STL源码剖析:配置器的更多相关文章

  1. <STL源码剖析>配置器

    1.stl六大部件 容器:各种数据结构,包括vector,list,deque,set,map等等 算法:各种常用算法,sort,search 迭代器:容器和算法之间的粘合器 防函数:类似于函数 配接 ...

  2. STL源码剖析 — 空间配置器(allocator)

    前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...

  3. STL源码剖析之空间配置器

    本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...

  4. 《STL源码剖析》环境配置

    首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...

  5. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  6. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  7. (原创滴~)STL源码剖析读书总结1——GP和内存管理

    读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...

  8. STL"源码"剖析

    STL"源码"剖析-重点知识总结   STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...

  9. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  10. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

随机推荐

  1. C#多线程编程(一)进程与线程

    一. 进程 简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间.一个程序想要并发执 ...

  2. MyBatis入门学习-连接oracle实现CURD基本操作

    目录 前言 导入oracle库 配置 环境配置 配置引用 配置映射 查询 单条件查询 多条件查询 通过类字段传递参数 通过Map接口传参 Param注解 插入 更新 删除 字段映射 参考文献 前言 本 ...

  3. 吃货联盟订餐系统 源代码 Java初级小项目

    咳咳,今天博主给大家写一个小的项目:吃货联盟订餐系统.博主不是大神(互联网架构师的路上ing),也是小白一个,不过是刚入门的小白^_^.项目功能也很简单:只是模拟日常的订餐流程呦,所以有错误以及功能不 ...

  4. [源码解析] 从TimeoutException看Flink的心跳机制

    [源码解析] 从TimeoutException看Flink的心跳机制 目录 [源码解析] 从TimeoutException看Flink的心跳机制 0x00 摘要 0x01 缘由 0x02 背景概念 ...

  5. Maven的pom文件依赖提示 ojdbc6 Missing artifact,需要手动下载并导入maven参考

    eg: 需要 ojdbc6.jar 的下载地址 https://www.oracle.com/database/technologies/jdbcdriver-ucp-downloads.html c ...

  6. JAVA 字节流 与 字符流 的区别

    字节流与和字符流的使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同呢? 字节流 在操作时本身不会用到缓冲区(内存),是文件本身直接操作的 字符流 在操作时使用了缓冲区,通过缓冲区再操作文 ...

  7. jmeter使用小结(一)

    jmeter是用来做接口压力测试的工具.这里只是简单介绍一下使用,大家可以自行查看帮助文档, 1.打开jmeter工具,创建线程组任务 2.添加配置元件,根据需要选择设置 3.添加采样器,这里是htt ...

  8. MongoDB快速入门教程 (3.3)

    3.4.聚合 3.4.1.什么是聚合? MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果.有点类似sql语句中的 count(*) 例如上图 ...

  9. METS介绍

    METS介绍 首页  >  关于METS > METS介绍   医护英语水平考试(Medical English Test System, 以下简称:METS)是由教育部考试中心与中国国际 ...

  10. IDEA 2020.1 插件市场无法找到官方的汉化包解决办法

    问题: idea 终于更新了2020.1版本,新增了好多的特性,官方也终于支持了中文语言包,但是下载后在插件市场无法找到官方的汉化包 解决: 去IDEA插件中心 (https://plugins.je ...