内存管理–简单内存池的实现

内存池的作用

  • 减少空间占用

    使用 malloc 分配内存时 , malloc 会多分配一部分空间作为 cookie

    cookie 的作用是记录分配的内存大小等信息, 方便 free 回收

    C++中的::operator new(全局中的 new)和::operator free,其内部都是调用 malloc 或者 free

    使用内存池, 可以让多次 new 申请的内存, 只需要分配 malloc 内存池时的 cookie 就可以了, 大大减少了 cookie 导致的内存浪费

  • 减少频繁分配内存时的开销

    自己实现内存池, 一次性分配较多的空间, 也就意味这多次 new 申请内存时, 只需要一次调用 malloc 就可,减少了 malloc 的开销

    同时 delete 时,将内存返回内存池, 也能提高内存的复用


内存池 1.0

  • 让类内含一个指针,在分配的时候方便链表使用
  • 重载 new 操作符和 delete 操作符
  • 含有静态成员内存池大小和当前头指针

[1.0]实现细节(易踩坑)

  • 类内创建的静态成员变量需要在类外初始化

    初始化方式例如int dog::num = 0;

  • 重载 new 和 delete 的时候返回值类型和参数类型有要求

    new : 返回值类型必须是*void**, 第一个参数必须是**size_t**

    delete: 返回值类型必须是**void, 第一个参数必须是*void**,第二个参数必须是size_t**

    重载 new 和 delete 都需要是静态方法(因为使用该方法的时候 , 成员还未初始化) 但编译器默认为静态,加不加无所谓

  • 申请内存池的时候,需要**转换成类指针类型** , 转换会取其前几位地址

  • 切块时**利用指针++会增加一个类型的大小**的特性,来填入链表

  • 到末尾时要将最后节点的 next 置为 nullptr!!!

    否则当内存池为空时 , 继续进入下一个指针造成栈溢出(因为是通过判断指针是否为空来确定内存池是否为空)

  • delete 重载时 , 也需要注意转换成类的类型,否则**可能会造成回收的内存大小出错**

  • for 循环串成链表时**条件中cur != &header[MAX_BLOCK - 1]要记得使用&取地址**

    因为 cur 为指针,而 header[MAX_BLOCK - 1]为最后一个成员的内容


[1.0]成品代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class testMryCrl_1 {
private:
testMryCrl_1* next;
static int MAX_BLOCKS;
static testMryCrl_1* header;
public:
int data;
testMryCrl_1(int data = 0) :data(data) {}

static void* operator new(size_t size) {
testMryCrl_1* ret;
//建立内存池
//判断当前内存池是否存在
if (header) {

}
else {
//内存池不存在,申请分配内存池
ret = header = static_cast<testMryCrl_1*>(::operator new(MAX_BLOCKS * sizeof(testMryCrl_1)));
//将申请的内存切块并串成链表
for (; ret != &header[MAX_BLOCKS - 1]; ret++) {
ret->next = ret + 1;
}
ret->next = nullptr;
}
ret = header;
header = header->next;
return ret;
}
static void operator delete(void* cur, size_t size) {
//回收内存
(static_cast<testMryCrl_1*>(cur))->next = header;
header = static_cast<testMryCrl_1*>(cur);
}
};

//初始化类内静态成员
int testMryCrl_1::MAX_BLOCKS = 20;
testMryCrl_1* testMryCrl_1::header;

[1.0]缺点分析

  • 类内维护一个指针,占用较大内存

    一般实际操作中 , 类的大小都不会很大 , 维护一个指针 , 则会造成内存浪费

    尤其在类的内存占用比较小的时候, 效果尤其明显

  • 代码实现较为复杂,代码复用性不高

    每当有一个类需要使用该内存池时,都需要类声明的时候写上重载的部分, 违背软件工程的思想

  • 当不断进行新的内存池扩充时,最后会**导致内存池的容量越来越大**

    初步想法是类似于哈希表中对哈希碰撞的处理 : 当内存池的数目超过一定大小,则全部回收,重新申请内存池

    暂时不考虑实现


内存池 2.0

  • 使用**union 联合体**来实现内存的复用性

    因为考虑到指针只有在 new 分配内存时才会有作用,与 data 部分不会同时使用


union 联合体

  • 一个结构存在多种身份,但是某一时刻下,只能会使用一种身份时, 为了节省内存而产生的结构

    又称共用体, 其 内存占用以 union 中定义的变量最大者为准

  • 联合体声明时,内部变量声明需要使用 ;

  • 无名联合体和有名称的联合体表示含义不同

    • 无名联合体可以视为对成员变量的声明

      使用时可以直接使用声明的变量(同一时刻只能使用联合体内的一个成员,其他成员会视为未初始化)

    • 有名称的联合体可以视为结构的声明

      使用时需要初始化结构, 将该联合体视为一种类型进行声明

  • 初始化其中一个成员, 其他成员则视为未初始化状态

  • 本质是公用一个地址空间


[2.0]实现细节

  • 如果有多个成员变量将内存池 1.0 中的成员变量,整合成一个结构体
  • union 中定义该类的指针和数据的结构体

[2.0]成品代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class testMryCrl_2 {
private:
static int MAX_BLOCKS;
static testMryCrl_2* header;
public:
testMryCrl_2(int data) :data(data) {}
union {
int data;
testMryCrl_2* next;
};

static void* operator new(size_t size) {
testMryCrl_2* ret;
//建立内存池
//判断当前内存池是否存在
if (header) {

}
else {
//内存池不存在,申请分配内存池
ret = header = static_cast<testMryCrl_2*>(::operator new(MAX_BLOCKS * sizeof(testMryCrl_2)));
//将申请的内存切块并串成链表
for (; ret != &header[MAX_BLOCKS - 1]; ret++) {
ret->next = ret + 1;
}
ret->next = nullptr;
}
ret = header;
header = header->next;
return ret;
}
static void operator delete(void* cur, size_t size) {
//回收内存
(static_cast<testMryCrl_2*>(cur))->next = header;
header = static_cast<testMryCrl_2*>(cur);
}
};

//初始化类内静态成员
int testMryCrl_2::MAX_BLOCKS = 20;
testMryCrl_2* testMryCrl_2::header;

[2.0]缺点分析

  • 代码复用性不高
  • 回收机制有待优化

内存池 3.0

  • 通过面向对象思想 , 将内存分配的动作封装成类,加以复用

  • 使用时,让类内内含一个静态的分配器对象


[3.0]实现细节

  • 了解指针真正的含义,理解**为什么可以在另一个类内定义链表指针**

    指针的指向的是该内存的第一个字节, 然后通过该指针的类型来确定需要取多少字节作为数据

  • **指针类型不同**的情况下 , 如何实现链表的切割和初始化

    将指针指针转换成 char*类型, 因为 char 的大小为 1 字节

  • malloc 分配内存的时候,怎么**确定分配内存的大小**

    利用使用 new 和 delete 时, 编译器自动传入的 sizet 参数**(new 操作的对象的大小, 相当于 sizeof(T))_**

  • 使用时, 需要确保类的大小不小于一个指针的大小!!!

    链表操作时,需要转换成obj*的类型,而指针的大小为 4 字节, 所以需要类的大小不小于一个指针的大小


[3.0]成品代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class myAlloc {
private:
struct obj {
struct obj* next; //维护链表
};
obj* header = nullptr;
const int MAX_BLOCK = 10; //内存池的大小
public:
void* alloc(size_t size) {
obj* ret;
if (!header) {
//申请内存池
size_t len = size * MAX_BLOCK;
ret = header = static_cast<obj*>(::operator new(len));
//对内存池进行切块并串成链表
for (int i = 0; i < MAX_BLOCK; i++) {
ret->next = (obj*)((char*)ret + size); //转换成char*是因为char的占用为1字节
ret = ret->next;
}
ret->next = nullptr;
}
ret = header;
header = header->next;
return ret;
}
void dealloc(void* p, size_t) {
((obj*)p)->next = header;
header = (obj*)p;
}
};


class testMryCrl_3 {
private:
int data2;
static myAlloc alloc;
public:
int data;

static void* operator new(size_t size) {
return alloc.alloc(size); //交给myAlloc处理
}
static void operator delete(void* p, size_t size) {
return alloc.dealloc(p, size);//交给myAlloc处理
}
testMryCrl_3(int data):data(data){}
};

myAlloc testMryCrl_3::alloc; //静态成员, 类内定义, 类外初始化

[3.0]缺点分析

  • 不同类需要使用该内存池的时候, 需要定义的内容其实是固定的
  • 回收内存机制有待优化
  • 需要确保使用该分配器的类的大小不小于一个指针的大小!!!

内存池 4.0

  • 通过使用宏定义, 将需要重复的部分封装成一个宏,提高代码可读性

[4.0]实现细节

  • 使用宏定义, 换行需要使用\
  • 类外声明时,通过宏获得类型
  • 将 static 方法声明称 protected

[4.0]成品代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class myAlloc {
private:
struct obj {
struct obj* next; //维护链表
};
obj* header = nullptr;
const int MAX_BLOCK = 10; //内存池的大小
public:
void* alloc(size_t size) {
obj* ret;
if (!header) {
//申请内存池
size_t len = size * MAX_BLOCK;
ret = header = static_cast<obj*>(::operator new(len));
//对内存池进行切块并串成链表
for (int i = 0; i < MAX_BLOCK; i++) {
ret->next = (obj*)((char*)ret + size);
ret = ret->next;
}
ret->next = nullptr;
}
ret = header;
header = header->next;
return ret;
}
void dealloc(void* p, size_t) {
((obj*)p)->next = header;
header = (obj*)p;
}
};
#define INSIDE_ALLOC protected: static myAlloc alloc;\
public:static void* operator new(size_t size){return alloc.alloc(size);}\
static void operator delete(void* p, size_t size){return alloc.dealloc(p, size);}

#define OUTSIDE_ALLOC(T) myAlloc T::alloc;

class testMryCrl_4 {
INSIDE_ALLOC //宏定义
private:
int data2;
public:
int data;

testMryCrl_4(int data):data(data){}
};

OUTSIDE_ALLOC(testMryCrl_4)