从基本的Cpp内存管理构件到高级内存管理器
Doug Lea自1986年起就潜心研究malloc算法
零 overview 内存分配的每一个层面
一 从C++ primitives介绍起
1 这几种基础原件的使用
// malloc and free
void *p1 = malloc(512);
free(p1);
// new and delete
complex<int>* p2 = new complex<int>;
delete p2;
//::operator new and :: operator delete
void *p3 = :: operator new(512); //其实内层还是malloc and free
::operator delete(p3);
//allocator from cpp standrad lib
#ifdef __GNUC__
void* p4 = alloc::allocate(512);
alloc::dellocate(p4,512);
#endif __GNUC__
2 new / operator new / malloc 三者的关系
在上一节中已经说过,new expression的详细过程
new 操作符的执行过程:
- 调用operator new分配内存 ;
- 调用构造函数在operator new返回的内存地址处生成类对象;(用户是不能做这个动作的 vc6可以gcc不可以(vc6在这方面还是不严谨) )
operator new 中使用了malloc来分配内存,如果内存不够分配了malloc即将失败,会调用__callnewh来释放一部分内存,这个机制是自己可以设置的.
如果你真的想直接调用ctor,换个思路,可以运用placement new:new( p ) Complex(1,2);
其兄弟delete expression:
- 先调用析构函数:pc->~Complex();
- 调用operator delete(pc); 释放内存 //析构函数可以被直接调用
operator delete 中使用了free来释放内存
3 array new/delete expression
Complex* pca = new Complex[3]; //唤起3次ctor
delete[] pac; //唤起3次dtor 如果不带[] 就会引起内存泄露
对于这类一次性new/delete多个对象的,其使用一次new/delete,底层都会唤起多次ctor/dtor,同时this会指向不同的object. 至于如何实现这个机制,那就要谈谈cookie的使用,你可以把cookie看做malloc和free的约定,在malloc分配内存时会在那一块内存边上附带一个cookie,其标记着something about bookkeeping,保存着其中分配了多少个对象.dtor被唤起时,free会获取到这个数值,从而dtor会被唤醒这么多次数,同时this指针也会跟着变动,使得每次都能都指向不同的object
这种使用 new object[n] 同时实例化多个对象的方法有个缺点,那就是无法使用参数设初值,可以用循环搭配placement new来初始化
4 placement new
placement new 允许我们将object建构于allocated memory中
没有所谓的placement delete ,因为placement new 根本没有分配memory
更倾向与把placement理解为一种定点的构造函数,在分配好的内存上调用构造函数
placement new只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
#include <new>
char* buf = new char[sizeof(Complex) *3];
Complex* pc = new(buf) Complex(1,2);
delete [] buf;
/*new(buf)那一行 被编译器转化为:*/
Complex *pc;
try{
void* mem = operator new(sizeof(Complex),buf); //void* operator new(size_t size,void* loc ) {return loc;}
pc = static_cast<Complex*> (mem);
pc->Complex::Complex(1,2);
}
catch(std::bad_alloc)
{
//若allocation失败就不执行constructor
}
1.用placement new 解决buffer的问题
问题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上。
2.增大时空效率的问题
使用new操作符分配内存需要在堆中查找足够大的剩余空间,显然这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。
彻底了解手上的三把刀子之后
C++应用程序,分配内存的途径:
//应用程序
//使用表达式 (new/delete expression) 不可改变 不可重载
Foo* p = new Foo(x);
---
delete p;
//将上面的new expression详细解释起来就是
Foo* p = (Foo*) operator new(sizeof(Foo)); //------->没有重载时调用全局的 ::operator new(size_t) 里面就是 CRT FUNC:malloc() (delete亦然,可以重载但是很少见)
new(p) Foo(x);
---
p->~Foo();
operator delete(p):
//所谓内存管理的一部分事情就是在重载这个operator new/delete 接管这个动作
//在类中重载 优先级更高
//可以考虑做一个内存池
//思考cookie的耗用情况
Foo::operator new(size_t);
让我们试图简单重载试试:
这里我们在类X中重载了operator new,这是最常见的做法。
运行结果为: