为什么要进行内存管理?
用malloc申请的内存中会保存此次申请的大小及相关调试cookie,这些信息在连续申请内存时是多余的,因为申请的每个对象的内存大小都一样;malloc会调用系统调用向操作系统申请内存,这涉及到上下文切换。所以我们内存管理的目的:
- 尽量减少malloc的次数
- 减少申请的内存cookie大小
1. C++为内存管理提供的基本组件
operator new/operator delete
原型
void* operator new(size_t size) // size为要申请的内存大小(字节)
void operator delete(void* deadObj, size_t size)
malloc/free
void* malloc(size_t size)
void free(void* deadObj)
placement new/placement delete
注意:placement new并不会申请内存,只是在已申请的内存上做构造函数的操作,只有placement new调用失败时,才会调用相应的placement delete来处理构造失败的情况
使用
void* p = operator new(sizeof(int));
int* pi = static_cast<int*>(p);
new(p)int(10);
1.1调用new, delete背后进行的操作
struct Foo{
Foo(int _i):i(_i){}
int i;
}
int* p = new Foo(10);
int* p = new Foo(10) 背后的机制
1. void* mem = operator new(sizeof(Foo));
2. Foo* pc = static_cast<Foo*>(mem);
3. pc->Foo::Foo(10); // vc下可以这么调用,gnuc会失败
new(pc)Foo(10) // 也可用placement new操作
operator new操作具体的步骤
主要的动作为malloc, 当malloc失败时,也就是oom,调用自己的new handler处理失败的场景,可能的实现,释放暂时用不到的内存,让本次调用返回可用内存。
new handler具体的目的有两个1.让跟多的内存可用2.调用abort()或exit()
2. 自己实现per-class allocator
2.1 version1
class Screen {
public:
Screen(int x) :i(x) {}
int get() { return i; }
void* operator new(size_t);
void operator delete(void*, size_t);
private:
Screen* next;
static Screen* freeStore;
static const int screenChunk;
private:
int i;
};
void* Screen::operator new(size_t size){
Screen* p;
if (!freeStore) {
size_t chunk = screenChunk*size;
// 申请一大块内存
freeStore = p = reinterpret_cast<Screen*>(new char[chunk]);
// 将小块内存用链表连接起来
for (; p != &freeStore[screenChunk - 1]; ++p) {
p->next = p + 1;
}
p->next = 0;
}
p = freeStore;
freeStore = freeStore->next;
return p;
}
void Screen::operator delete(void* p,size_t) {
(static_cast<Screen*>(p))->next = freeStore;
freeStore = static_cast<Screen*>(p);
}
Screen* Screen::freeStore = 0;
const int Screen::screenChunk = 24;
2.2 version2
改进:加入embedded pointer 节省next指针的开销,因为next指针只有在分配内存和回收内存的时候会使用,而返回给用户时只会使用该对象的内容,而不会使用next指针,所以用union包裹next指针和类对象
class Airplane {
private:
struct AirplaneRep {
unsigned long miles;
char type;
};
private:
union {
AirplaneRep rep;
Airplane* next;
};
public:
unsigned long getMiles() { return rep.miles; }
char getType() { return rep.type; }
void set(unsigned long m, char t)
{
rep.miles = m;
rep.type = t;
}
public:
// 这里加不加static都可以,编译器会给我们加上,因为类对象未构造,其行为未知。
static void* operator new(size_t size);
static void operator delete(void* deadObj, size_t size);
private:
static const int BLOCK_SIZE;
static Airplane* headOfFreeList;
};
Airplane* Airplane::headOfFreeList; // 空闲链表头
const int Airplane::BLOCK_SIZE = 512; // 空闲链表为空时,一次申请的块数
void* Airplane::operator new(size_t size){
// 继承会导致size不等,这里不做过多考虑
if (size != sizeof(Airplane))
return ::operator new(size);
Airplane* p = headOfFreeList;
if (p)
headOfFreeList = p->next;
else {
Airplane* newBlock = static_cast<Airplane*>
(::operator new(BLOCK_SIZE*sizeof(Airplane)));
// 将申请的内存用链表串起来
for (int i = 1; i < BLOCK_SIZE - 1; ++i)
newBlock[i].next = &newBlock[i + 1];
newBlock[BLOCK_SIZE - 1].next = 0;
p = newBlock;
headOfFreeList = &newBlock[1];
}
return p;
}
void Airplane::operator delete(void* deadObj, size_t size) {
if (deadObj == 0) return;
if (size != sizeof(Airplane)) {
::operator delete(deadObj);
return;
}
//回收该对象,指针的next指向空闲链表头, 然后调整空闲链表头部为deadObj
(static_cast<Airplane*>(deadObj))->next = headOfFreeList;
headOfFreeList = static_cast<Airplane*>(deadObj);
}
2.3 抽象为allocator
给没一个类配置一个allocator
class myAllocator
{
private:
struct obj {
struct obj* next; //embedded pointer
};
public:
void* allocate(size_t);
void deallocate(void*, size_t);
//void check();
private:
obj* freeStore = nullptr;
const int CHUNK = 5; //小一點方便觀察
};
void* myAllocator::allocate(size_t size)
{
obj* p;
if (!freeStore) {
//linked list 是空的,所以攫取一大塊 memory
size_t chunk = CHUNK * size;
freeStore = p = (obj*)malloc(chunk);
//cout << "empty. malloc: " << chunk << " " << p << endl;
//將分配得來的一大塊當做 linked list 般小塊小塊串接起來
for (int i = 0; i < (CHUNK - 1); ++i) { //沒寫很漂亮, 不是重點無所謂.
p->next = (obj*)((char*)p + size);
p = p->next;
}
p->next = nullptr; //last
}
p = freeStore;
freeStore = freeStore->next;
//cout << "p= " << p << " freeStore= " << freeStore << endl;
return p;
}
void myAllocator::deallocate(void* p, size_t)
{
//將 deleted object 收回插入 free list 前端
((obj*)p)->next = freeStore;
freeStore = (obj*)p;
}
myAllocator的使用
class Foo {
public:
long L;
string str;
static myAllocator myAlloc; // Macro
public:
Foo(long l) : L(l) { }
static void* operator new(size_t size) // Macro
{ return myAlloc.allocate(size); }
static void operator delete(void* pdead, size_t size) // Macro
{
return myAlloc.deallocate(pdead, size);
}
};
myAllocator Foo::myAlloc; // Macro
2.4 Macro 替换
将version3中的可重复使用的代码,写成宏即可
3.GNU2.9 alloc实现详解
一级分配器(>128字节),直接调用malloc
二级分配器(<=128字节),调用alloc中重载的operator new()
解释:
假设申请对象大小为n。
- 首先这个alloc是工作于所有对象上的,申请的对象大小为n向上取整取到8的倍数的大小
- 维持一个内存池,当内存池为空时申请 n202 + roundUP(累计申请量)。
将一块内存返回给用户,剩下19块,用链表连接起来,剩下20*n的大小作为内存池 - 16个链表各维持一个空闲链表,当空闲链表为空时,首先考虑在内存池(pool)中切分出想要的大小(最多20),若不够一个, 则malloc一大块,重复2的步骤。