在C++中,咱们习惯用new
申请堆中的内存,配套地,使用delete
释放内存。express
class LiF; LiF* lif = new LiF(); // 分配内存给一个LiF对象 delete lif; // 释放资源 lif = nullptr; // 指针置空,保证安全
与C的malloc
相比,咱们发现,new
操做在申请内存的同时还完成了对象的构造,这也是new
运算符作的一层封装。数组
从new
这个例子能够看出,C++的内存管理大有门道,而内存管理也是C++中最为重要的一部分。在硬件层之上的第一层封装就是操做系统,高级语言编写的程序也将做为进程在这里接受进程调度,其中就涉及到内存的分配。从这个意义上理解,能够说,内存是向操做系统申请的(不严格正确)。安全
在C++应用层(Application),咱们最经常使用的是C++ primitive(原语)操做,new
、new[]
、new()
、::operator new()
等,申请内存。在primitive之上,C++的Library还为咱们提供了各类各样的allocator(容器,或者说分配器),如std::allocator
,能够经过这些容器分配内存,但其实容器仍是经过new
和delete
运算符去实现内存的申请与释放。在new
之下,则是Microsoft的CRT库提供的malloc
和free
,new
操做是对malloc的封装。再往下就是操做系统的API。这些内存管理的API的关系大体以下:cookie
一般,咱们会使用new
在堆中申请一块内存,并把这块内存的地址保存到一个指针,这个操做就是new操做,但严格来讲,它其实应该称为new expression(new表达式)。app
LiF* lif = new LiF(); // new expression
但其实,new
是一个复合操做,一般会被编译器转换为相似以下的形式:函数
LiF* lif; try { void* mem = operator new(sizeof(LiF)); // apply for memory lif = static_cast<LiF*>(mem); // static type conversion lif->LiF::LiF(); // constructor } catch(std::bad_alloc) { // exception handling }
operator new
申请足够存放对象大小的内存;在try/catch
块的第一句,new expression调用了operator new
,它的原型是:操作系统
// 位于<vcruntime_new.h> _Ret_notnull_ _Post_writable_byte_size_(_Size) _NODISCARD _VCRT_ALLOCATOR void* __CRTDECL operator new( size_t _Size ); _Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size) _NODISCARD _VCRT_ALLOCATOR void* __CRTDECL operator new( size_t _Size, std::nothrow_t const& ) noexcept;
而在operator new()
会去调用::operator new()
,最后,::operator new()
的内部其实是调用了malloc
。operator new()
的工做就是经过malloc
不断申请内存,直到申请成功。在operator new的第二个重载中能够看到,这是一个noexcept
的函数,由于咱们能够认为,内存的申请老是能够成功的,由于在operator new()
内部,每当申请失败时,他都会调用一次new handler,能够把new handler理解为一个内存管理策略,它会释放掉一些不须要的内存,以便当前的malloc
能够申请到内存。能够说,operator new的工做就是申请内存。指针
在new拆解获得的第三步,它调用了对象的构造函数,并且在表达上比较特殊:lif->LiF::LiF();
。编译器经过对象指针直接调用了对象的构造函数,但若是咱们在程序中这样写,编译通常是没法经过的,这不是源代码的语法。在上面的语句中,咱们已经完成了内存的分配工做,显然这一步是在进行对象的构造,这个操做也被称为placement new,即定点构造,在指定的内存块中构造对象。code
new expression是operator new和placement new的复合。对象
在咱们再也不须要某一个对象时,一般使用delete
析构该对象。delete操做严格来讲,与new
对应,它应该称为delete expression(delete表达式)。
delete lif; // delete expression lif = nullptr;
一样,delete
也是一个复合操做,一般会被编译器转换为相似以下的形式:
lif->~LiF(); // destructor operator delete(lif); // free the memory
在delete操做的第二步,其实是执行了operator delete()
,它的原型是:
void __CRTDECL operator delete( void* _Block, size_t _Size ) noexcept;
而operator delete
实际上是调用了全局的::operator delete()
,::operator delete()
又调用了free
进行内存的释放。
也就是说,new
和delete
是对malloc
和free
的一层封装,这也对应了上面图中的内容。
array new即new[]
,顾名思义,它用于构造一个对象数组。
class LiF { public: LiF(int _lif = 0): lif(_lif) {} int lif; }; LiF* lifs = new LiF[3]; // right LiF* lifs = new LiF[3](); // right LiF* lifs = new LiF[3](1); // wrong, no param accepted LiF* lifs = new LiF[3]{1}; // right, but only lifs[0].lif equals 1
array new
的工做是申请一块足以容纳指定个数的对象的内存(在本例中是3个LiF对象)。在前两种写法中,array new调用的是默认构造函数,这种状况下只能默认构造对象,但若是又想要给对象赋予非默认的初值,那么就须要使用到placement new了。
LiF* lifs = new LiF[3]; LiF* p = lifs; for (int i = 0; i < 3; ++i) { new(p++)LiF(i+1); // placement new cout << lifs[i].lif << endl; }
直观地,placement new并不会分配内存,它只是在已分配的内存上构造对象。对应地,使用array new构造的对象须要使用array delete释放内存。
delete[] lifs;
相较于array new,array delete不须要提供数组长度参数。这是由于,在使用array new构造对象的时候,还有一块额外的空间用于存放cookie,也就是这块内存的一些信息,其中就包括这个内存块的大小和对象的数量等等。
class LiF { public: //... ~LiF() { cout << "des" << endl; } }; delete[] lifs; // array delete
此时咱们显式地定义析构函数,而且在析构函数被调用时打印信息。在运行到delete[]
的时候,程序就会根据cookie中的信息,准确地释放对应的内存块,本例中,“des”会被打印三次,即3个对象的析构函数都被调用了。此时若是错误地调用delete而非array delete,那么就可能会发生内存泄漏。
delete lifs; // delete
这时只会调用一次析构函数,但本例中并不会发生泄漏,这个简单的类中并无包含其余对象。再看下面这种状况:
class LiF2 { public: LiF2() : lif(new LiF()) {} LiF2(const LiF& _lif) : lif(new LiF(_lif.lif)) {} ~LiF2() { delete lif; lif = nullptr; } private: LiF* lif; }; LiF2* lif2 = new LiF2[3]; delete lif2; // call "delete" by mistake
这时,因为错误地使用了delete,析构函数只会被调用一次,也就是说,还有另外两个对象,虽然对象自己被销毁了,但对象中的lif
指针所指的对象却没有被销毁,即:对象自己不会发生泄漏,泄漏的是对象中指针保存的内存。
以前提到的new()
操做以及new expression拆解的第三步,其实都是placement new。在主动使用placement new时,它的通常格式为:
new(pointer)Constructor(params); // or ::operator new(size_t, void*);
它的做用是:把对象(object)构造在已分配的内存(allocated memory)中。一样也能够在vcruntime_new.h
中找到相关定义:
#ifndef __PLACEMENT_NEW_INLINE #define __PLACEMENT_NEW_INLINE _Ret_notnull_ _Post_writable_byte_size_(_Size) _Post_satisfies_(return == _Where) _NODISCARD inline void* __CRTDECL operator new(size_t _Size, _Writable_bytes_(_Size) void* _Where) noexcept { (void)_Size; return _Where; } inline void __CRTDECL operator delete(void*, void*) noexcept { return; } #endif
能够看到,placement new并无作任何工做,它只是把咱们传递的指针又return
了回来。结合下面的例子就不难理解这个逻辑。
class LiF { public: LiF(int _lif = 0): lif(_lif) {} int lif; }; LiF* lifs = new LiF[3]; // array new LiF* lif = new(lifs)LiF(); // placement new
咱们在array new获得的LiF
对象数组中的第一个对象上使用了placement new,一样拆解这个new操做能够获得相似上面普通new
的一个try/catch
块:
LiF* lif; try { void* mem = operator new(sizeof(LiF), lifs); // placement new lif = static_cast<LiF*>(mem); // static type conversion lif->LiF::LiF(); // constructor } catch(std::bad_alloc) { // exception handling }
此外,在__PLACEMENT_NEW_INLINE
宏还包含了一个placement delete的定义:
inline void __CRTDECL operator delete(void*, void*) noexcept { return; }
能够看到,它也是不作任何工做的,所谓的placement delete只是为了形式上的统一。