C++: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(原语)操作,new
、new[]
、new()
、::operator new()
等,申请内存。在primitive之上,C++的Library还为我们提供了各种各样的allocator(容器,或者说分配器),如std::allocator
,可以通过这些容器分配内存,但其实容器还是通过new
和delete
运算符去实现内存的申请与释放。在new
之下,则是Microsoft的CRT库提供的malloc
和free
,new
操作是对malloc的封装。再往下就是操作系统的API。这些内存管理的API的关系大致如下:
再谈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做了什么
- 调用
operator new
申请足够存放对象大小的内存; - 把申请到的内存交给我们的指针;
- 最后调用构造函数构造对象。
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的工作就是申请内存。
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做了什么
- 调用对象的析构函数;
- 释放内存。
operator delete
在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和array delete
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
指针所指的对象却没有被销毁,即:对象本身不会发生泄漏,泄漏的是对象中指针保存的内存。
深入placement new
之前提到的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只是为了形式上的统一。
总结
- 内存的申请释放可以在不同层面上进行,但只要是在操作系统之上,都是基于malloc/free。
- 在C++ primitive层,通常使用new和delete系列,new是对malloc的封装,delete是对free的封装。
- 通常new是指new expression。严格来说,new的含义有三种:new expression、operator new和placement new。new expression是operator new和placement new的复合,operator new负责内存的申请,placement new负责对象的构造;此外还有new[]。
- 所有的内存申请/释放操作都必须配套使用。
C++:Memory Management的更多相关文章
- Memory Management in Open Cascade
Open Cascade中的内存管理 Memory Management in Open Cascade eryar@163.com 一.C++中的内存管理 Memory Management in ...
- Java (JVM) Memory Model – Memory Management in Java
原文地址:http://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java Understanding JV ...
- Objective-C Memory Management
Objective-C Memory Management Using Reference Counting 每一个从NSObject派生的对象都继承了对应的内存管理的行为.这些类的内部存在一个称为r ...
- Operating System Memory Management、Page Fault Exception、Cache Replacement Strategy Learning、LRU Algorithm
目录 . 引言 . 页表 . 结构化内存管理 . 物理内存的管理 . SLAB分配器 . 处理器高速缓存和TLB控制 . 内存管理的概念 . 内存覆盖与内存交换 . 内存连续分配管理方式 . 内存非连 ...
- ural1037 Memory Management
Memory Management Time limit: 2.0 secondMemory limit: 64 MB Background Don't you know that at school ...
- 如何展开Linux Memory Management学习?
Linux的进程和内存是两座大山,没有翻过这两座大山对于内核的理解始终是不完整的. 关于Linux内存管理,在开始之前做些准备工作. 首先bing到了Quora的<How can one rea ...
- Fixed Partition Memory Management UVALive - 2238 建图很巧妙 km算法左右顶点个数不等模板以及需要注意的问题 求最小权匹配
/** 题目: Fixed Partition Memory Management UVALive - 2238 链接:https://vjudge.net/problem/UVALive-2238 ...
- MIT-6.828-JOS-lab2:Memory management
MIT-6.828 Lab 2: Memory Management实验报告 tags:mit-6.828 os 概述 本文主要介绍lab2,讲的是操作系统内存管理,从内容上分为三部分: 第一部分讲的 ...
- Lifetime-Based Memory Management for Distributed Data Processing Systems
Lifetime-Based Memory Management for Distributed Data Processing Systems (Deca:Decompose and Analyze ...
随机推荐
- tf.nn.l2_loss()的用法
https://blog.csdn.net/yangfengling1023/article/details/82910536
- odoo添加顶部按钮实现自定义方法
一.效果图 自定义添加顶部按钮,实现自定义方法. 二.实现过程 1.需要用到三个文件,tree_view_button.js.tree_view_odoo.xml.base.xml三个文件,文件目录如 ...
- ubuntu安装后的基本配置及常用软件的安装
文章作者:foochane 原文链接:https://foochane.cn/article/2019061501.html 内容简介 当前Ubuntu版本:ubuntu 18.04,具体操作如下: ...
- GCN和GCN在文本分类中应用
1.GCN的概念 传统CNN卷积可以处理图片等欧式结构的数据,却很难处理社交网络.信息网络等非欧式结构的数据.一般图片是由c个通道h行w列的矩阵组成的,结构非常规整.而社交网络.信息网络 ...
- [目录] ASP.Net Core 搭建微服务网站
本项目采用ASP.Net Core微服务技术,搭建博客和Saas平台. 全文将围绕(1)设计模式 (2)敏捷开发 目的: 结构足够合理,代码足够优美,扩展性.可读性.易维护性做到最优. 以下目录仅为 ...
- 解决 java Could not resolve resource location pattern [classpath:sql/*.xml] 的问题
时间过的真快,转眼间已经有2年了.2年里发生了太多的事,有些事依稀还记得,但更多的已经忘记.忘记了那些烦恼与忧愁,以为自己会快乐,才发现一切并不是以自己的意志为转移.终于在迷途中迷失了自己, 再也回不 ...
- P1963 [NOI2009]变换序列 倒叙跑匈牙利算法
题意 构造一个字典序最小的序列T,使得 Dis(i, Ti) = di,其中i是从0开始的,Dis(x,y)=min{∣x−y∣,N−∣x−y∣} ,di由题目给定. 思路 二分图匹配,把左边的看成i ...
- zoj 3724 树状数组经典
问题:n个点,对于每个点i,都有一条连向i+1的有向边,另外有m条其他的有向边,有q个询问(u,v)求u到v的最短路 将m条有向边和q个询问对所表示的点对一起排序,(u,v)u大的排前,u一样的v ...
- 牛客小白月赛6 J 洋灰三角 数学
链接:https://www.nowcoder.com/acm/contest/136/J来源:牛客网 题目描述 洋灰是一种建筑材料,常用来筑桥搭建高层建筑,又称,水泥.混凝土. WH ...
- andriod开发--使用Http的Get和Post方式与网络交互通信
package com.example.a350773523.myapplication; import android.os.AsyncTask; import android.support.v7 ...