C++重拾之new/delete内存管理
C++中的存在三种new:
- new operator
- operator new
- placement new
与之对应便存在三种delete:
- delete operator
- operator delete
- placement delete
1. new/delete operator
new operator为通常所用的new运算符,c++内置运算符。不能被重载,其行为总是一致的。比如A* a = new A;
一个这样的new语句操作相当于三步操作:
- 调用operator new分配内存(如果类A重载了operator new,那么将调用A::operator new(size_t ),否则调用全局::operator new(size_t )
- 调用构造函数初始化内存空间
- 返回内存空间地址
与之对应的delete语句delete a
相当于:
- 调用析构函数析构对象
- 调用operater delete释放内存(如果类A重载了operator delete,那么将调用A::operator delete(void*),否则调用全局::operator delete(void* )
如下代码重载全局operator new/delete,同时类A也重载operator new/delete。
void * operator new(size_t size)
{
cout << "全局内存分配"<<endl;
return malloc(size);
}
void operator delete(void * p)
{
cout << "全局delete " << endl;
free(p);
}
class A
{
public:
A():m_val(5)
{
cout << "构造函数" << endl;
}
A(const A& other)
{
cout << "拷贝构造函数" << endl;
}
~A()
{
cout << "析构函数" << endl;
}
void * operator new(size_t size)
{
cout << "类内部内存分配"<<endl;
return malloc(size);
}
void operator delete(void * p)
{
cout << "类内部delete " << endl;
free(p);
}
int m_val;
};
int main()
{
A* a = new A;
cout<<a->m_val<<endl;
delete a;
return 0;
}
运行结果为(调用类A中operator new/delete):
注释掉类内的重载的operator new
/* void * operator new(size_t size)
{
cout << "类内部内存分配"<<endl;
return malloc(size);
}*/
运行结果为(调用全局operator new):
再注释掉类内的重载的operator delete
/*void operator delete(void * p)
{
cout << "类内部delete " << endl;
free(p);
}*/
运行结果为(调用全局operator delete):
再注释main中的delete语句
int main()
{
A* a = new A;
cout<<a->m_val<<endl;
//delete a;
return 0;
}
运行结果为(不析构和释放内存):
2. operator new/delete
new/delete函数,可重载,供new/delete operator调用。要实现不同的内存分配行为,应该重载operator new/delete,而不是new/delete。
3. placement new
placement new 是重载operator new的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此在删除该对象时,只需要调用对象的析构函数,不需用delete运算符来释放空间。
原型如下:
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型
中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
placement new的使用遵循以下步骤:
第一步 缓存提前分配(堆上或栈上)
第二步:对象的构造
第三步:使用
第四步:对象的析构(必须显式的调用类的析构函数进行销毁对象)
第五步:释放
跳过任何步骤就可能导致运行时间的崩溃,内存泄露,以及其它的意想不到的情况。
int main()
{
//堆上分配
int* buff = new int [sizeof(A)];
//栈上分配
int buf[10*sizeof(A)];
cout<<"buff:"<<buff<<endl;
cout<<"buf:"<<buf<<endl;
//对象构造
A* b = new (buff) A;
A* c = new (buf) A;
cout<<"b:"<<b<<endl;
cout<<"c:"<<c<<endl;
//对象使用
cout<<b->m_val<<endl;
cout<<c->m_val<<endl;
//对象析构
b->~A();
c->~A();
//堆上内存释放
delete [] buff;
return 0;
}
运行结果为(对象地址与提前分配地址相同):
placement new 的用处和好处:
placement new/delete 主要用途是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。例如可以先申请一个足够大的字符数组,然后当需要时在它上面构造不同类型的对象或数组。placement new不用担心内存分配失败,因为它根本不分配内存,它只是调用对象的构造函数。
placement new的好处:
1)在已分配好的内存上进行对象的构建,构建速度快。
2)已分配好的内存可以反复利用,有效的避免内存碎片问题。
4. placement delete
inline void operator delete (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。对一个指针施行delete绝不会导致调用placement delete。这意味对所有placement new我们必须同时提供一个正常的delete和一个placement版本。