STL源码剖析(1)空间分配器
配置器的接口
-
allocate:分配内存
-
deallocate:释放内存
-
construct:在已申请的内存上构造对象
-
destroy:析构对象
-
address:取某个对象的地址
-
const_address:常版本地址
-
max_size:返回可申请的最大空间
标准库的分配器
std::allocator,这个分配器类只是对原始的new和delete做了一层包装,效率很低不建议使用。
std::alloc:这个才是真正投入使用的
构造和析构
构造很容易,直接用placement new就行了
template<class T1, class T2>
inline void construct(T1 *p, const T2& value) {
new (p) T1(value); // placement new,在以分配的内存上构造
}
析构就比较麻烦了,有好几个版本,这里看最简单的,直接调用析构函数析构
template<class T>
inline void deconstruct(T *pointer) {
pointer->~T(); //调用析构
}
内存的申请与释放
这一块涉及到操作系统的一些知识。为了简单,我们不考虑多线程的各种同步问题。
最简单的内存分配,就是用使用new和delete,底层是c的malloc和free。但是,如果每次申请的内存都是很小一块,会导致内存碎片的问题。
STL设置了两级的分配器。如果分配的内存很大,那么就直接使用一级分配器。如果很小,就使用内存池,让二级分配器来分配内存。
一级分配器
一级分配器非常简单,主要功能如下
- 用malloc分配内存
- realloc重新调整大小
- free释放内存
- 提供函数指针,供用处处理内存不足的情况
二级分配器
二级分配器用来分配小于128k的内存,大于这个数的内存交给一级分配器完成。
二级分配器用内存池来维护一系列内存块。首先,分配器保存了一个freeList,这个freeList是一个链表数组,保存了8的倍数的内存块,并以链表维护。
解释一下,freeList的定义是这样的:void *freeList[16],其中freeList[0]保存一个链表,这个链表每个节点的大小都是8个字节,同理,freeList[1]保存一个节点为16字节大小的链表。那这样第16个就是保存128字节大小的内存块。
有个问题是链表的next指针,如果单独在每个节点中加个next,开销还是很大的。这里我们可以用union,当内存节点在freeList中的时候,就当作一个next指针。当该内存节点被申请后,就当作真实数据。
还有一个需要注意的是,我们会对申请的字节做一个8字节对齐(便于内存的分配)。比如我们申请2个字节,会自动对齐到8字节;申请15字节会对齐到16字节。
处理内存的工具
这是几个全局函数,在大规模内存上构造对象。
uninitialized_copy
// 将first和last之间的对象产生一个拷贝,并用于构造result到result+(last-frist)之间的内存。
uninitialized_copy(first, last, result);
uninitialized_fill
// 用T构造first到last之间的内存
uninitialized_fill(first, last, T);
uninitialized_fill_n
// 用T构造first到first + n之间的内存
uninitialized_fill_n(first, n, T);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现