侯捷C++内存分配课程总结三:内存分配模型
文章内容参照于侯捷 C++内存分配系列教程
文章目录
回顾:重载new行为的目的
在之前的文章中,我门了解到malloc在分配内存的时候会额外分配出一些不能为用户所使用的内存块。当我们多次分配内存时,这一个开销会变得越来越大。而我们重载new行为的最重要的目的就是减少调用malloc分配内存所产生的额外开销。
一、内存分配模型:内存池
我们在开始讨论这种内存分配模型之前需要先明确一个问题:我们无法改变malloc的动作(除非我们去修改源码)。这也意味着我们无法改变每次调用malloc都会产生的额外开销,所以,我们去进行优化的方法就只能时减少对malloc的调用次数。
实现方法:
- 在第一次分配内存时,调用malloc一次性分配一大块内存,称为内存池
- 当需要分配内存时,首先检查内存池是否有足够的容量,如果有就直接从内存池中为其分配内存,若不存在则调用malloc为内存池再次补充一大块内存。
- 当已经被分配出的内存释放时,将它还于内存池
如此,我们可以很简单的减少了malloc的调用次数,尽管每一次调用malloc都要产生额外开销,但相比直接调用malloc会优化了很多。这里所说的只是大体的实现,下面我们去看更多的细节。
二、具体实现
1.C++prime中的实现
代码来自于C++prime
//对于类的定义
class Screen {
public:
Screen(int x) :i(x) {
};
int get() {
return i; }
void * operator new(size_t);//重载new的行为
void operator delete(void *, size_t);//重载delete的行为
private:
Screen *next;//仅用于内存池的中,指向下一块可用的内存
static Screen * freeStore;//内存池头节点的指针
static const int screenChunk;//每次分配的数量
private:
int i;
};
Screen *Screen::freeStore = 0;
const int Screen::screenChunk = 24;
我们首先分析这个类的定义:
- 构造函数、数据成员i和方法get,这三个成员我们不必去关心
- 重载了operator new 和 operator delete, 很明显,我们要改变new的行为就只能通过重载这两个函数
- 一根指向本体的指针:这根指针的作用是,当当前成员还在内存池中时,用于连接下一块内存
- 一根指向内存池头节点的指针,指向所维护的内存池的第一个可用内存
- 一个代表每次分配的数量的int,这也就是我之前说的分配一大块内存的那个一大块的数量
//重载的operator new()
void *Screen::operator new(size_t size)
{
Screen *p;
if (!freeStore) {
//当链表为空时,就申请一大片内存
size_t chunk = screenChunk * size;//计算需要分配的大小
//分配内存并将指针转型
freeStore = p =reinterpret_cast<Screen*>(new char[chunk])