C++内存管理
第一讲 malloc/free
-
malloc算法对于系统运行的效率非常重要,DL malloc是很有效率的一个算法
-
越使用底层的功能接口,可能程序的移植性越差
-
内存分配工具
分配 释放 种类 可否重载 malloc() free() C函数 不可 new delete C++表达式 不可 ::operator new() ::operator delete() C++函数 可 allocator ::allocate() allocator::deallocate() C++标准库 可以自由设计并搭配容器 -
使用范例
// 以下均为申请512个字节的内存空间 void *p1 = malloc(512); free(p1); complex<int>* p2 = new complex<int>; delete p2; // 以下实质调用的是malloc和free void *p3 = ::operator new(512); ::operator delete(p3); // allocator有接口的标准,但是不同的编译器实现的标准可能存在差异 #ifdef _MSC_VER //以下函数都是non-static,要通过object调用,以下分配3个ints. int* p4 = allocator<int>().allocate(3,(int*)0);// 第二参数默认值 allocator<int>().deallocate(p4,3); #endif #ifdef _BORLANDC_ //以下函数都是non-static,要通过object调用,分配5个整数类型空间 int* p4 = allocator<int>().allocate(5);// ()表示创建临时对象 allocator<int>().deallocate(p4,5); #endif #ifdef _GNUC_//以下函数都是static // 分配512个无指定类型的字节 void* p4 = alloc::allocate(512); alloc::deallocate(p4,512); //以下兩函數都是 non-static,定要通過 object 調用。以下分配 7 個 ints. void* p4 = allocator<int>().allocate(7); allocator<int>().deallocate((int*)p4,7); //以下兩函數都是 non-static,定要通過 object 調用。以下分配 9 個 ints. void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9); __gnu_cxx::__pool_alloc<int>().deallocate((int*)p5,9); #endif
-
new的本质(分配–转型–实例化)
Complex *pc = new Complex(1, 2); // 用c语言进行其编译功能的解释 Complex *pc; try{ void *mem = operator new(sizeof(Complex));//分配内存,实质调用malloc pc = static_cast<Complex*>(mem);// 内存转型,赋值给指针 pc->Complex::Complex(1, 2);// 调用构造函数实例化内存(赋值) }catch(std::bad_alloc){ // 若allocation失败就不执行constructor }
-
operator new的本质
// 第二参数保证函数不抛出异常 void *operator new(size_t size, const std::nothrow_t &){ void *p; while((p=malloc(size)) == 0){// 如果内存耗尽导致分配失败 (实质调用malloc) _TRY_BEGIN if(_callnewh(size) == 0)// 调用自定义函数进行处理 break; _CATCH(std::bad_alloc) return 0; _CATCH_END } return p; }
-
delete的本质(析构–释放)
delete pc; // 使用c语言进行翻译 pc->~Complex();// 先析构 operator delete(pc);// 后释放 //operator delete源码 void __cdecl operator delete(void *p)_THROW0(){ free(p); }
-
构造函数不能直接调用,析构函数可以
-
使用array new进行内存申请,必须使用array delete进行内存释放,如果不使用可能会导致内存泄漏
-
debugger模式下,malloc实际申请内存
- 申请内存首尾各4个字节的cookie
- 32字节的debugger Header
- 真正的申请的数据存储空间
- 整体对齐到16的倍数的padding
-
placement new:运行将对象建构在已分配的内存中
#include <new> char *buf = new char[sizeof(Complex) * 3]; Complex *pc = new(buf)Complex(1, 2);// buf是已分配的空间 ··· delete []buf; // Complex *pc = new(buf)Complex(1, 2);的编译解释 Complex *pc; try{ void *mem = operator new(sizeof(Complex), buf);//直接返回buf,无作用 pc = static_cast<Complex*>(mem);// 转型 pc->Complex::Complex(1, 2);// 调用构造函数赋值 }
-
没有placement delete,因为placement new没有分配内存
-
重载全局的 ::operator new/::operator delete
// 更改全局的内存管理函数影响很大,尽量不用进行更改 void* myAlloc(size_t size){ return malloc(size); } void myFree(void *ptr){ return free(ptr); } inline void *operator new(size_t size){ cout<< "global new()"; return myAlloc(size); } inline void *operator new[](size_t size){ cout << "global new[]()"; return myAlloc(size); } inline void operator delete(void *ptr){ cout<< "global delete()"; myFree(ptr); } inline void operator delete[](void *ptr){ cout<< "global delete[]()"; myFree(ptr); }
-
重载类中的operator new / operator delete
- 类成员operator new()可以重载多个版本,前提是每个版本必须有独特的参数列,其中第一参数必须是对象大小site_t
Foo *p = new Foo;编译后的动作 ··· delete p; // Foo *p = new Foo;编译后的动作 try{ void *mem = operator new(sizeof(Foo));// 申请内存 p = static_cast<Foo*>(mem);// 指针类型转换 p->Foo::Foo(1, 2);// 构造函数赋值 } // delete p;编译后的动作 p->~Foo(); operator delete(p); // 类内重载 class Foo{ public: void *operator new(size_t); void operator delete(void*, size_t);// 第二参数可以不写 void *operator new(size_t size, void *start){ return start; } void *operator new(size_t, long extra){ return malloc(size+extra); } void *operator delete(void*, long){ cout<< "operator delete(void*, long)"<< endl; } }
-
内存分配好后如果失败,必须要将内存进行回收。如果自定义的opereator new执行出错,会调用相应的析构函数
第二讲 std::allocator
-
内存管理的核心目标就是提高速度、降低空间使用
-
内存池是由固定大小数组组成,这些数组通过指针形成链表,可能分散在不同的内存区域
-
使用单链表模拟allocator
// 表示内存管理是一块池塘,使用链表进行管理 #include <cstddef> #include <iostream> using namespace std; 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; }; 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; 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){ // 将deleted object 插回free list前端 (static_cast<Screen*>(p))->next = freeStore; freeStore = static_cast<Screen*>(p); }
-
union同一个数据使用不同的形式进行展示
-
加入embedded pointer 节省next指针的开销,实现更高性能的内存池
/* 嵌入式指针工作原理:空闲内存块需要使用指针进行链接,一旦分配出去,这一部分即可被再利用,从而节省空间(32位系统4个字节,即内存块要大于4个字节) */ 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); }
-
抽象为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; }
-
类内对象或函数专门为该类服务,则使用static修饰。静态对象要在类外进行定义。
-
当operator new没能力为你分配申请的内存,会抛出一个std::bad_alloc exception。抛出异常之前会调用一个可以由用户指定的handler
// 分配失败的补救措施 typedef void*(new_handler)(); new_handler set_new_handler(new_handler p)throw();
-
两个新关键字
- =default:使用该函数的默认版本(拷贝构造函数、拷贝赋值函数、析构函数)
- =delete:不允许适用该函数
class Foo{ public: Foo() =default; Foo(const Foo&) =delete; Foo& operator=(const Foo&) =delete; ~Foo() =default; ··· };
-
VC6标准库,其std::allocator的实现,只是以::operator new和::operator delete完成alloccate()和deallocate(),没有其他设计
#ifndef _FARQ #define _FARQ #define _PDFT ptrdiff_t #define _SIZE size_t #endif template<class _Ty> class allocator{ public: typedef _SIZE size_type; typedef _PDFT difference_type; typedef _Ty _FARQ *pointer; typedef _Ty value_type; pointer allocate(size_type _N, const void *){ return (_Allocate((difference_type)_N, (pointer)0)); } void deallocate(void _FARQ *_P, size_type){ operator delete(_P); } }; // _Allocate的定义 template<class _Ty> inline _Ty _FARQ *_Allocate(_PDFT _N, _Ty _FARQ*){ if(_N < 0) _N = 0; return ((_Ty _FARQ*)operator new((_SIZE)_N *sizeof(_Ty))); }
第三讲 loki::allocator
-
GCC的内存管理全部掌握在分配器手中,Loki可以将回收的内存归还操作系统
-
内存管理核心数据结构——三个指针=内存数据块首部 + 未分配数据块首部 + 内存数据块尾部
-
单向链表头插法可用于最高优先权的操作
-
&*i
,其中 i 是迭代器,表示取其指向元素的首地址 -
VicinityFind方法
for(;;){ if(向上未到尽头) 顺序查找; if(向下未到尽头) 顺向查找; }
-
Loki allocator特点
- 精简强悍,手段暴力
- 使用array取代list,使用index取代pointer
- 可以很简单的判断chunk是否全回收,即可以将memory归还给操作系统
- 有Defferring(暂缓归还)的能力
- 是一个用来分配小块并无cookie的内存块的分配器
-
基本内存分配:当容器需要内存就调用operator new进行分配,当容器释放内存就调用operator delete。相比内存池,分配内存速度慢但是可以有效利用系统资源
-
不直接调用malloc,而使用operator new的原因是:虽然malloc速度快,但是不可以重载,失去了灵活性
-
大块内存的使用可以避免小内存块的cookie,增加内存空间的利用率
-
分配器追求的都是充分利用系统资源,eg:减少使用cookie
-
vector的本质是三个指针
- start:指向已使用的内存空间首地址
- finish:指向已使用的内存空间的末地址
- end_of_storage:总分配的内存空间末尾
-
17minp54