C++内存管理总结

为什么要进行内存管理?

用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操作具体的步骤
C++内存管理总结
主要的动作为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()

C++内存管理总结
C++内存管理总结

解释:
假设申请对象大小为n。

  1. 首先这个alloc是工作于所有对象上的,申请的对象大小为n向上取整取到8的倍数的大小
  2. 维持一个内存池,当内存池为空时申请 n202 + roundUP(累计申请量)。
    将一块内存返回给用户,剩下19块,用链表连接起来,剩下20*n的大小作为内存池
  3. 16个链表各维持一个空闲链表,当空闲链表为空时,首先考虑在内存池(pool)中切分出想要的大小(最多20),若不够一个, 则malloc一大块,重复2的步骤。
上一篇:21.10.17模拟 绵羊


下一篇:C++友元函数学习笔记