STL空间配置器
这其中的 new 和 delete 都包含两阶段操作:
-
对于 new 来说,编译器会先调用 ::operator new 分配内存;然后调用 Obj::Obj() 构造对象内容。
-
对于 delete 来说,编译器会先调用 Obj::~Obj() 析构对象;然后调用 ::operator delete 释放空间。
为了精密分工,STL allocator 决定将这两个阶段操作区分开来。
-
对象构造由 ::construct() 负责;对象释放由 ::destroy() 负责。
-
内存配置由 alloc::allocate() 负责;内存释放由 alloc::deallocate()
construct() 函数接受一个指针 P 和一个初始值 value,该函数的用途就是将初值设定到指针所指的空间上。
destroy() 函数有两个版本,第一个版本接受一个指针,准备将该指针所指之物析构掉。直接调用析构函数即可。
#include <new.h>//欲使用placement new。
//版本1
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value);
//在分配好的内存中,重新构建对象,p指向分配好的内存,然后重新创建T1,通过T2初始化。
//并调用T1::T1(value)构造函数
}
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
} //直接调用对象里面的析构函数而已
//版本2,接受迭代器进行析构范围对象
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}
其中 destroy() 只截取了部分源码,全部实现还考虑到特化版本,比如判断元素的数值类型 (value type) 是否有 trivial destructor 等限于篇幅,完整代码请参阅《STL 源码剖析》。
2. 内存管理
无论使用第一级配接器(malloc_alloc_template)或是第二级配接器(
#ifdef __USE_MALLOC
...
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc; // 令alloc为第一级配置器
#else
...
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc; // 令alloc为第二级配置器 !_USE_MALLOC
#endif
template <class T, class Alloc>
class simple_alloc {
public:
static T* allocate(size_t n) {
return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof(T));
}
static T* allocate(void) {
return (T*) Alloc::allocate(sizeof(T));
}
static void deallocate(T* p, size_t n) {
if (0 != n)
Alloc::deallocate(p, n * sizeof(T));
}
static void deallocate(T* p) {
Alloc::deallocate(p, sizeof(T));
}
};
__malloc_alloc_template
就是第一级配置器,__default_alloc_template
SGI 第一级配置器的 allocate() 和 reallocate() 都是在调用malloc() 和 realloc() 不成功后,改调用 oom_malloc()
和oom_realloc()
/*******************第一级内存配置器模板***********************/
template <int inst> // 非类型参数
class __malloc_alloc_template {
private:
//函数指针,用来处理内存不足情况。oom = out of memory
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); // malloc搞起来
if (0 == result)
result = oom_malloc(n); // 分配失败,使用传进来的函数
return result;
}
static void deallocate(void *p, size_t /* n */)
{
free(p); // 直接使用free释放空间
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz); // realloc搞起来,扩大或缩小原分配内存空间。
if (0 == result)
result = oom_realloc(p, new_sz);
return result;
}
static void (* set_malloc_handler(void (*f)() ) )() // 回调函数,将自己的函数传进来,传进函数指针,返回函数指针,所以看起来比较复杂
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
template <int inst>
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; } // 没有设置则抛出异常
(*my_malloc_handler)(); // 调用异常处理例程,释放内存,用户定义。
result = malloc(n); // 再次尝试分配内存。
if (result)
return(result);
}
}
template <int inst>
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);
}
}
2.2.1 自由链表free_list
union obj {//free_list节点构造
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
-
从第一个字段看,obj 可以看做一个指针,指向链表中的下一个节点;
-
template <bool threads, int inst>
class __default_alloc_template {
private:
enum {__ALIGN = 8}; // 8字节对齐
enum {__MAX_BYTES = 128}; // 上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // free_list数目
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
} // 将bytes上调至8的倍数
__PRIVATE:
union obj { // free_list节点构造
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
private:
static obj * __VOLATILE free_list[__NFREELISTS]; // 16个free_lists,指针数组,free_list是一个指针指向的数据类型也是指针
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
} // 根据区块大小,决定使用第几个free_list。
static void *refill(size_t n);
static char *chunk_alloc(size_t size, int &nobjs); // 配置一大块空间,可容纳n个objs的大小的size区块
static char *start_free; // 内存池起始
static char *end_free; // 内存池结束
static size_t heap_size; // 堆大小
public:
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;
if (n > (size_t) __MAX_BYTES) {//大于128字节,调用第一级配置器
return(malloc_alloc::allocate(n));
}
my_free_list = free_list + FREELIST_INDEX(n); // 寻找16个free list中适当的一个
result = *my_free_list; // 取出链表首节点
if (result == 0) { // 如果对应链表为空,准备重新增加链表节点
void *r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result -> free_list_link; // free_list[n]中更改为下一个节点地址
return (result); // 将第一个节点地址返回给外部使用
}
// 释放内存,
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;
if (n > (size_t) __MAX_BYTES) {
malloc_alloc::deallocate(p, n);
return;
}
my_free_list = free_list + FREELIST_INDEX(n); // 寻找对应freelist
q -> free_list_link = *my_free_list; // 调整freelist,回收区块
*my_free_list = q;
}
static void * reallocate(void *p, size_t old_sz, size_t new_sz); // 声明成员函数
};
2.3 标准接口函数 allocate
2.3.1 空间申请
我们知道第二级配置器拥有配置器的标准接口函数 allocate()。此函数首先判断区块的大小,如果大于 128bytes –> 调用第一级配置器;小于128bytes–> 就检查对应的 free_list(如果没有可用区块,就将区块上调至 8 倍数的边界,然后调用 refill(), 为 free list 重新填充空间。
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;
if (n > (size_t) __MAX_BYTES) { // 大于128字节,调用第一级配置器
return(malloc_alloc::allocate(n));
}
my_free_list = free_list + FREELIST_INDEX(n); // 寻找16个free list中适当的一个
result = *my_free_list; // 取出链表首节点
if (result == 0) { // 如果对应链表为空,准备重新增加链表节点
void *r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result -> free_list_link; // free_list[n]中更改为下一个节点地址
return (result); // 将第一个节点地址返回给外部使用
};
NOTE:每次都是从对应的 free_list 的头部取出可用的内存块。然后对free_list 进行调整,使上一步拨出的内存的下一个节点变为头结点。
2.3.1 空间释放
NOTE:通过调整 free_list 链表将区块放入 free_list 的头部。
2.3.3 重新填充 free_lists
当发现 free_list 中没有可用区块时,就会调用 refill() 为free_list 重新填充空间。
-
新的空间将取自内存池(经由 chunk_alloc() 完成);
-
缺省取得20个新节点(区块),但万一内存池空间不足,获得的节点数可能小于 20。
//模板中成员的定义,用于重新给链表分配空间
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 20; // 默认每个链表含有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); // 返回首节点
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?