C++内存管理总结
2021/10/1 7:43:12
本文主要是介绍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操作具体的步骤
主要的动作为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的步骤。
这篇关于C++内存管理总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-11国产医疗级心电ECG采集处理模块
- 2025-01-10Rakuten 乐天积分系统从 Cassandra 到 TiDB 的选型与实战
- 2025-01-09CMS内容管理系统是什么?如何选择适合你的平台?
- 2025-01-08CCPM如何缩短项目周期并降低风险?
- 2025-01-08Omnivore 替代品 Readeck 安装与使用教程
- 2025-01-07Cursor 收费太贵?3分钟教你接入超低价 DeepSeek-V3,代码质量逼近 Claude 3.5
- 2025-01-06PingCAP 连续两年入选 Gartner 云数据库管理系统魔力象限“荣誉提及”
- 2025-01-05Easysearch 可搜索快照功能,看这篇就够了
- 2025-01-04BOT+EPC模式在基础设施项目中的应用与优势
- 2025-01-03用LangChain构建会检索和搜索的智能聊天机器人指南