设为首页 加入收藏

TOP

C++:Memory Management(一)
2019-09-30 16:42:17 】 浏览:83
Tags:Memory Management

浅谈C++内存管理

new和delete

C++中,我们习惯用new申请堆中的内存,配套地,使用delete释放内存。

class LiF;
LiF* lif = new LiF(); // 分配内存给一个LiF对象
delete lif; // 释放资源
lif = nullptr; // 指针置空,保证安全

与C的malloc相比,我们发现,new操作在申请内存的同时还完成了对象的构造,这也是new运算符做的一层封装。

内存是怎样申请的

new这个例子可以看出,C++的内存管理大有门道,而内存管理也是C++中最为重要的一部分。在硬件层之上的第一层封装就是操作系统,高级语言编写的程序也将作为进程在这里接受进程调度,其中就涉及到内存的分配。从这个意义上理解,可以说,内存是向操作系统申请的(不严格正确)。

在C++应用层(Application),我们最常用的是C++ primitive(原语)操作,newnew[]new()::operator new()等,申请内存。在primitive之上,C++的Library还为我们提供了各种各样的allocator(容器,或者说分配器),如std::allocator,可以通过这些容器分配内存,但其实容器还是通过newdelete运算符去实现内存的申请与释放。在new之下,则是Microsoft的CRT库提供的mallocfreenew操作是对malloc的封装。再往下就是操作系统的API。这些内存管理的API的关系大致如下:

memory-management-1

再谈new和delete

new expression

通常,我们会使用new在堆中申请一块内存,并把这块内存的地址保存到一个指针,这个操作就是new操作,但严格来说,它其实应该称为new expression(new表达式)

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
}

new做了什么

  1. 调用operator new申请足够存放对象大小的内存;
  2. 把申请到的内存交给我们的指针;
  3. 最后调用构造函数构造对象。

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()的内部实际上是调用了mallocoperator new()的工作就是通过malloc不断申请内存,直到申请成功。在operator new的第二个重载中可以看到,这是一个noexcept的函数,因为我们可以认为,内存的申请总是可以成功的,因为在operator new()内部,每当申请失败时,他都会调用一次new handler,可以把new handler理解为一个内存管理策略,它会释放掉一些不需要的内存,以便当前的malloc可以申请到内存。可以说,operator new的工作就是申请内存。

placement new

在new拆解得到的第三步,它调用了对象的构造函数,而且在表达上比较特殊:lif->LiF::LiF();。编译器通过对象指针直接调用了对象的构造函数,但如果我们在程序中这样写,编译一般是无法通过的,这不是源代码的语法。在上面的语句中,我们已经完成了内存的分配工作,显然这一步是在进行对象的构造,这个操作也被称为placement new,即定点构造,在指定的内存块中构造对象。

new expression是operator new和placement new的复合。

delete expression

在我们不再需要某一个对象时,通常使用delete析构该对象。delete操作严格来说,与new对应,它应该称为delete expression(delete表达式)

delete lif; // delete expression
lif = nullptr;

同样,delete也是一个复合操作,通常会被编译器转换为类似如下的形式:

lif->~LiF(); // destructor
operator delete(lif); // free the memory

delete做了什么

  1. 调用对象的析构函数;
  2. 释放内存。

operator delete

在delete操作的第二步,实际上是执行了operator delete(),它的原型是:

void __CRTDECL operator delete(
    void*  _Block,
    size_t _Size
) noexcept;

operator delete其实是调用了全局的::operator delete()::operator delete()又调用了free进行内存的释放。

也就是说,newdelete是对mallocfree的一层封装,这也对应了上面图中的内容。

array new和array delete

array newnew[],顾名思义,它用于构造一个对象数组。

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++)Li
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇符号表-定长数组实现符号表 下一篇多态

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目