STL源码剖析:配置器
作用:对内存的管理
接口:申请和释放
内容:
几个全局函数
一级配置器
二级配置器
准备知识
POD是什么:
Plain Old Data简称POD,表示传统的C语言类型;与POD类型对应的是非POD类型,表示C++独有的类型
区别:POD类型可以直接新进行批量拷贝,非POD类型需要单个对象逐个拷贝,不能批量拷贝
非POD类型不能直接拷贝的原因之一:
如何类成员中包含引用或指针,如果直接拷贝,就会出现两个引用或指针指向同一块内存情况,很有可能直接导致内存多次释放和相互影响的危险
new做了什么:
class Foo { ... } Foo *f = new Foo();
delete f;
调用全局
::operator new
函数申请内存调用
Foo:Foo()
构造对象
delete做了什么:
调用全局
Foo:~Foo()
析构对象调用
::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函数
拷贝一块区域内的数据到另一块区域,源:
first
到last
,目的:result
到result+(last-first)
根据POD数据和非POD数据进行区分,POD数据直接拷贝,非POD数据一个个调用替换构造函数拷贝
将
char
和wchar
单独处理,使用函数特化技术,效率更高难点解析:
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函数
填充一块内存中的内容为指定数据,内存为:
first
到last
,数据为:`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函数功能基本相同,只是入参有区别
数据区:
first
到first+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源码剖析:配置器的更多相关文章
- <STL源码剖析>配置器
1.stl六大部件 容器:各种数据结构,包括vector,list,deque,set,map等等 算法:各种常用算法,sort,search 迭代器:容器和算法之间的粘合器 防函数:类似于函数 配接 ...
- STL源码剖析 — 空间配置器(allocator)
前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...
- STL源码剖析之空间配置器
本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...
- 《STL源码剖析》环境配置
首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
- 【转载】STL"源码"剖析-重点知识总结
原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...
- (原创滴~)STL源码剖析读书总结1——GP和内存管理
读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...
- STL"源码"剖析
STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- STL源码剖析之序列式容器
最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
随机推荐
- jQuery实现全选、反选、删除
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content ...
- Spring Boot 2.x基础教程:MyBatis的多数据源配置
前两天,我们已经介绍了关于JdbcTemplate的多数据源配置以及Spring Data JPA的多数据源配置,接下来具体说说使用MyBatis时候的多数据源场景该如何配置. 添加多数据源的配置 先 ...
- IDEA版本彩虹屁插件idea-rainbow-fart,一个在你编程时疯狂称赞你的 IDEA扩展插件
缘起 是否听说过程序员鼓励师,不久前出了一款vscode的插件rainbow-fart,可以在写代码的时候,匹配到特定关键词就疯狂的拍你马屁. vscode的下载尝试过,但是作为日常将IDEA作为主力 ...
- JavaScript基础对象创建模式之命名空间(Namespace)模式(022)
JavaScript中的创建对象的基本方法有字面声明(Object Literal)和构造函数两种,但JavaScript并没有特别的语法来表示如命名空间.模块.包.私有属性.静态属性等等面向对象程序 ...
- 一.5.序列化应用之服务器制造厂与型号app功能
1.环境准备: (python36env) [vagrant@CentOS7 apps]$ django-admin startapp manufacturer (1)激活:'manufacturer ...
- 从别人的代码中学习golang系列--01
自己最近在思考一个问题,如何让自己的代码质量逐渐提高,于是想到整理这个系列,通过阅读别人的代码,从别人的代码中学习,来逐渐提高自己的代码质量.本篇是这个系列的第一篇,我也不知道自己会写多少篇,但是希望 ...
- 07 . Kubernetes之Service
kubernetes有三种网络 1. Node Network 2. Pod Network 3. Cluster Network Service-网络代理模式 **userspce: 1.1- ** ...
- 曹工说面试:当应用依赖jar包的A版本,中间件jar包依赖B版本,两个版本不兼容,这还怎么玩?
背景 大一点的公司,可能有一些组,专门做中间件的:假设,某中间件小组,给你提供了一个jar包,你需要集成到你的应用里.假设,它依赖了一个日期类,版本是v1:我们应用也依赖了同名的一个日期类,版本是v2 ...
- day48 work
1 navicat自己玩一玩 2 练习题一定要搞懂 照着我的思路一遍遍的看敲 3 熟悉pymysql的使用 4 sql注入产生的原因和解决方法 了解 5 思考:如何结合mysql实现用户的注册和登录功 ...
- IBM & Howdoo – 区块链上的智能社交
原文链接:https://www.themsphub.com/ibm-howdoo-smart-social-on-the-blockchain 我们很高兴地宣布,我们成为了一个令人兴奋的新社交网络的 ...