记录学习的点点滴滴,参考侯捷<<C++内存管理>>
我们先重载一下C++的几个内存管理函数 operator new, operator new[], operator delete, operator delete[]
1.创建一个类
class Foo { private: int _id; //4 long _data;//4 string _str;//4 public: Foo():_id(0) { cout << "default ctor.this=" << this << " id=" << _id << endl; } Foo(int a):_id(a) { cout << "ctor.this=" << this << " id=" << _id << endl; } // virtual ~Foo() { cout << "dtor.this=" << this << " id=" << _id << endl; } //申请内存的函数必须是必须是静态的 调用这个函数时一般都是正在创建这个对象 所以当调用时你这个对象还不存在 所以需要声明成静态 static void* operator new(size_t size); static void operator delete(void* pdead, size_t size); static void* operator new[](size_t size); static void operator delete[](void* pdead, size_t size); };
上一节提到过operator new 和 operator delete的实现是基于malloc()和free() 这边也直接在函数内声明这两个函数
void* Foo::operator new(size_t size)//size 为将要申请的内存大小 { Foo* p = (Foo*)malloc(size); cout <<"operator new().sizeof()="<< size << " return=" << p <<endl; return p; //p 为内存起始点 } void Foo::operator delete(void* pdead, size_t size)//pdead 删除点 和上面的p为同一个位置 size 为将要删除的内存大小 { cout <<"operator delete.pdead=" << pdead << " size=" << size <<endl; cout << endl; free(pdead); } void* Foo::operator new[](size_t size) { Foo* p = (Foo*)malloc(size); cout <<"operator new[].size="<< size <<" return=" << p << endl; return p; } void Foo::operator delete[](void* pdead, size_t size) { cout<< "operator delete[].pdead=" << pdead << " size="<< size <<endl; cout << endl; free(pdead); }
下面调用一下测试函数
void test() { cout << "sizeof(Foo)="<<sizeof(Foo) << endl; Foo* p = new Foo(7); delete p; Foo* pArray = new Foo[5]; delete [] pArray; }
下图示侯捷在<<C++内存管理>> 在VC6上的内存管理图片
class A { int a; ... int j;//共10个int型变量 }
那么这个类的Debug版实际内存应该是下图这种
这个图片分为 白色的上下cookie 上下cookie占8字节 10个灰色的data数据占40字节 12字节的补全字节 和一个橙色的4字节空位,
上面我用的是Qt的编译器
看上面log的第7行 "operator new[].size=64 return = 0x8714c0" 起始内存点在0x8714c0.但是构造函数的内存起点是0x8714c4
那么这4个字节就是上图白色部分的cookie,它的内存分配应该是下图所示
那么当我们每创建一组这样的数据,那就会有上下两个cookie(8字节),那么当我们创建多组数据的话就会有很多组cookie,那么就会占用一些花销
有一种方法减少这种内存的花销,那就是内存池操作
内存池
先说一下内存池的优点
1.减少malloc的使用,提高运行效率
2.可以指定对齐方式
3.减少内存碎片,减少cookie
创建一个Screen类
class Screen { public: Screen(int x): i(x) { //todo } int get() { return i; } void* operator new(size_t); void operator delete(void*, size_t); private: Screen* next;//4bit static Screen* freeStore; static const int screenChunk;//想要创建多少组 private: int i; //4bit }; Screen* Screen::freeStore = 0; const int Screen::screenChunk = 24;
重载operator new(创建内存池)和operator delete
void* Screen::operator new(size_t size) { Screen* p; if(!freeStore) { //linked list是空的,所以申请一大块内存 size_t chunk = screenChunk * size; //192 Screen的内存大小为8共24组 24 * 8 = 192 freeStore = p = reinterpret_cast<Screen*>(new char[chunk]); cout << "startPisotion: " << p << endl; //将一大块内存分割成片段,当做linked list串接起来 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) { //将delete object插回 free list前端 (static_cast<Screen*>(p)) -> next = freeStore; freeStore = static_cast<Screen*>(p); }
测试代码
void test() { cout << sizeof(Screen) << endl; size_t const N = 10; Screen* p[N]; cout << "overload operator new" << endl; for(int i=0; i<N; i++) { p[i] = new Screen(i); } for(int i = 0; i<10; i++) { cout << p[i] << endl;//输出每个Screen的内存起点 } for(int i=0; i<N; i++) { delete p[i]; } cout << "glob operator new" << endl; Screen* q[N]; for(int i=0; i<N; i++) { q[i] = ::new Screen(i); } for(int i = 0; i<10; i++) { cout << q[i] << endl; } for(int i=0; i<N; i++) { ::delete q[i]; } }
测试结果
你会发现调用全局的和调用重载的函数结果差别会很大
全局operator new的默认对齐方式是16字节对齐而且会发现没有了cookie,而且减少了malloc得使用提高使用效率
测试代码
namespace wzj03_class12_15 { class Screen { public: Screen(int x): i(x) { //todo } int get() { return i; } void* operator new(size_t); void operator delete(void*, size_t); private: Screen* next;//4bit static Screen* freeStore; static const int screenChunk; private: int i; //4bit }; Screen* Screen::freeStore = 0; const int Screen::screenChunk = 24; void* Screen::operator new(size_t size) { Screen* p; if(!freeStore) { //linked list是空的,所以申请一大块内存 size_t chunk = screenChunk * size; //192 Screen的内存大小为8共24组 24 * 8 = 192 freeStore = p = reinterpret_cast<Screen*>(new char[chunk]); cout << "startPisotion: " << p << endl; //将一大块内存分割成片段,当做linked list串接起来 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) { //将delete object插回 free list前端 (static_cast<Screen*>(p)) -> next = freeStore; freeStore = static_cast<Screen*>(p); } void test() { cout << sizeof(Screen) << endl; size_t const N = 10; Screen* p[N]; cout << "overload operator new" << endl; for(int i=0; i<N; i++) { p[i] = new Screen(i); } for(int i = 0; i<10; i++) { cout << p[i] << endl; } for(int i=0; i<N; i++) { delete p[i]; } cout << "glob operator new" << endl; Screen* q[N]; for(int i=0; i<N; i++) { q[i] = new Screen(i); } for(int i = 0; i<10; i++) { cout << q[i] << endl; } for(int i=0; i<N; i++) { delete q[i]; } } }
这段代码是封装了一个小型内存池
namespace wzj03_class12_15_1 { class myAllocator { private: struct obj { struct obj* next; }; public: void* allocate(size_t); void deallocate(void*, size_t); private: obj* freeStore = nullptr; const int CHUNK = 5; }; void myAllocator::deallocate(void* p, size_t size) { cout << "myAllocator::deallocate" << "size: " << size <<endl; ((obj*)p)->next = freeStore; freeStore = (obj*)p; } void* myAllocator::allocate(size_t size) { cout << "myAllocator::allocate" << "size: " << size <<endl; obj* p; if(!freeStore) { size_t chunk = CHUNK * size; freeStore = p = (obj*)malloc(chunk); for(int i=0; i<(CHUNK-1); ++i) { p->next = (obj*)((char*)p + size); p = p->next; } p->next = nullptr; } p= freeStore; freeStore = freeStore -> next; return p; } class Foo { public: long L; string str; static myAllocator myAlloc; public: Foo(long l): L(l) { //todo } static void* operator new(size_t size) { return myAlloc.allocate(size); } static void operator delete(void* pdead, size_t size) { return myAlloc.deallocate(pdead, size); } }; myAllocator Foo::myAlloc; void test() { cout << sizeof(Foo) << endl; size_t const N = 5; Foo* p[N]; cout << "overload operator new" << endl; for(int i=0; i<N; i++) { p[i] = new Foo(i); } for(int i = 0; i<N; i++) { cout << p[i] << endl; } for(int i=0; i<N; i++) { delete p[i]; } cout << "glob operator new" << endl; Foo* q[N]; for(int i=0; i<N; i++) { q[i] = ::new Foo(i); } for(int i = 0; i<N; i++) { cout << q[i] << endl; } for(int i=0; i<N; i++) { ::delete q[i]; } } }