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_templatemalloc(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源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
随机推荐
- WeChair项目Alpha冲刺(2/10)
团队项目进行情况 1.昨日进展 Alpha冲刺第二天 昨日进展: 前端完成小程序首页的html+css设计 后端springboot项目搭建完成 详情参见github 数据库也初步建成一些表格, ...
- vue入门的第一天:v-clock、v-text、v-html的使用
vue入门的第一天 1. v-cloak v-cloak可以解决插值闪烁问题(防止代码被人看见),在元素里加入 v-cloak即可 html: <p v-cloak>{{msg}}< ...
- 看完这篇 HashMap,和面试官扯皮就没问题了
HashMap 概述 如果你没有时间细抠本文,可以直接看 HashMap 概述,能让你对 HashMap 有个大致的了解. HashMap 是 Map 接口的实现,HashMap 允许空的 key-v ...
- eclipse .project文件 .classpath文件的作用
.classpath文件的作用 可以参考.classpath文件的作用 .project文件的作用 确保你自己的eclipse能创建Java项目,并且正确编译运行helloworld,给eclipse ...
- HTML&CSS面试高频考点(三)
11. CSS隐藏元素的方式 /*占据空间,无法点击*/ visibility: hidden; position: relative; top: -999em; /* 不占据空间,无法点击 */ p ...
- 并发07--线程池及Executor框架
一.JAVA中的线程池 线程池的实现原理及流程如下图所示: 如上图所示,当一个线程提交到线程池时(execute()或submit()),先判断核心线程数(corePoolSize)是否已满,如果未满 ...
- day18__文件操作
一.3 种模式 r: 只读模式, r+: 读写模式,覆盖开头内容 w: 写模式,全覆盖 (如果是没有的文件则重新创建空文件) a+: 读写模式,从最开头写,覆盖开头内容 (如果是没有的 ...
- Flask项目实战:创建电影网站(3)后台的增删改查
添加预告 根据需求数据库创建表格 需求数据库,关键字title logo # 上映预告 class Preview(db.Model): __tablename__ = "preview&q ...
- js语法基础入门(3)
3.数据类型 3.1.数据类型学习重点 前面我们通俗的讲了,数据类型其实就是对数据进行了分类,那么,在js中到底把数据分成了几类?这些类的名称叫什么?每个分类下面有那些值?这些问题是需要记清楚的,例如 ...
- 【博弈】HDU - 5963 朋友
题目 B君在围观一群男生和一群女生玩游戏,具体来说游戏是这样的: 给出一棵n个节点的树,这棵树的每条边有一个权值,这个权值只可能是0或1. 在一局游戏开始时,会确定一个节点作为根.接下来从女生开始,双 ...