C++系列总结——new和delete
前言
"new
和malloc()
有什么区别",这是一个很常见的C++面试题。我的回答是"new
等于malloc()
后再选择性执行构造函数"。执行流程上是这样的,但是这样的回答是有纰漏的,比如没有考虑异常。下面就仔细聊一聊new
,了解了new
就了解了delete
。
选择性的含义是有构造函数就会执行,没有构造函数则不会执行。
new是什么
new
是C++的一个关键字,这个关键字有两种用法
通常我们都是使用new
表达式,当然我们也可以直接使用new
运算符。
int* a = new int; // new表达式
int* b = (int*)operator new( sizeof(int) ); // new运算符
new表达式
调试一下下面这段代码,看看编译器是如何对待new
表达式的。
class A
{
public:
A(){}
~A(){}
int a;
};
int main()
{
A* a = new A;
return 0;
}
截取一段汇编代码
=> 0x000000000040061f <+9>: mov $0x4,%edi # 0x4就是A的大小,作为参数值传递
0x0000000000400624 <+14>: callq 0x400510 <_Znwm@plt> # 单步调试,就会进入operator new( unsigned long ) 函数
0x0000000000400629 <+19>: mov %rax,%rbx # rax里存储了返回的内存地址
0x000000000040062c <+22>: mov %rbx,%rdi # 将分配的内存地址作为参数
0x000000000040062f <+25>: callq 0x400644 <A::A()> # 如果没有构造函数的话,这里就不会调用了
从上,我们发现当使用new
表达式时,编译器做的工作就是计算出类型的大小,然后将该大小作为参数调用operator new()
,最后调用构造函数在operator new()
返回的地址上做初始化。因为分配的大小是编译器自己计算出来的,所以即使operator new()
的返回值是void*,编译器也不会告警。
new运算符
常用的new
运算符原型有如下几种
1. void* operator new( std::size_t count ) throw( std::bad_alloc ); // 对应着 new T
2. void* operator new( std::size_t count, const std::nothrow_t& tag) throw(); // 对应着 new(std::nothrow) T
3. void* operator new( std::size_t count, void* ptr ) throw(); // 对应着 new(ptr) T,直接在ptr所指的内存上执行T的构造函数进行初始化。
1和2的区别仅在于1会抛出异常,而2不会。
一定不要写出类似下面的代码,当分配不出内存时,其实际结果是不符合预期的。
A* a = new A;
if( NULL == a ){
return false;
}
除去异常这块,我们能看到new
运算符和malloc()
几乎是一模一样的:指定分配的内存大小,返回void*类型。如果去看内部实现的话,我们更会发现operator new()
其实就是调用了malloc()
,毕竟没有必要重复造轮子嘛。
new[]
new[]
表达式和new
表达式本质上没有什么差别,都是分配内存并初始化,只不过new[]
表达式是一次性对分配多个对象的内存并初始化。
常用的new[]
表达式,对应以下几种new[]
运算符
1. void* operator new[]( std::size_t count ) throw( std::bad_alloc ); // 对应着 new T[N]
2. void* operator new[]( std::size_t count, const std::nothrow_t& tag) throw(); // 对应着 new(std::nothrow) T[N]
3. void* operator new[]( std::size_t count, void* ptr ) throw(); // 对应着 new(ptr) T[N],实际是直接返回ptr
调试代码可以发现new[]
运算符实际就是调用了对应的new
运算符。
为了讲解new[]
表达式的一些实现,需要从delete[]
开始说起。当我们写delete[] a;
时,并没有像new[]
那样指定元素个数,那么编译器如何知道需要执行几次析构函数呢?答案很简单,就是new[]
的时候,编译器会多分配点空间用来保存元素个数。
为什么
delete[]
不指定元素个数?我想还是为了减少程序员的工作量。
让我们来看看代码
class A
{
public:
A(){}
~A(){}
private:
int a;
};
int main()
{
A* a = new A[1];
}
从下面的汇编可以看出实际分配的大小是12个字节,a地址的前8个字节保存了元素个数
=> 0x0000000000400623 <+13>: mov $0xc,%edi # count是12
0x0000000000400628 <+18>: callq 0x400500 <_Znam@plt> # 调用operator new[]()
0x000000000040062d <+23>: mov %rax,%rbx # 将operator new[]()的返回值,放入rbx
0x0000000000400630 <+26>: movq $0x1,(%rbx) # 记录元素个数是1
0x0000000000400637 <+33>: lea 0x8(%rbx),%rax # 往后移动8字节,开始调用构造函数
delete[]
会根据记录的元素个数执行完指定个数的析构函数,然后往前偏移8字节,调用free()
释放内存。
我们知道编译器是不会做多余的事,如果没有析构函数需要执行的话,还需要多分配空间来保存元素个数么?答案是不需要!
class A
{
public:
A(){}
private:
int a;
};
int main()
{
A* a = new A[1];
}
可以看到分配的大小就是4字节。
0x0000000000400623 <+13>: mov $0x4,%edi
0x0000000000400628 <+18>: callq 0x400500 <_Znam@plt> # 调用operator new[]()
因此如果没有析构处理的必要的话,就不要写析构函数了,省内存。
到这里,我们很自然的就能知道为什么new
和delete
以及new[]
和delete[]
要配套使用了。
后话
new
和delete
是很基础的知识了,日常只需要记住配套使用,不遗漏delete就足够了。
可以想想,下面这段代码运行时会有问题么,会破坏malloc内存管理结构、内存泄漏或者踩内存么?
A* a = new A;
a->~A();
delete[] (char*)a;
C++系列总结——new和delete的更多相关文章
- SpringBoot系列教程JPA之delete使用姿势详解
原文: 190702-SpringBoot系列教程JPA之delete使用姿势详解 常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿 ...
- Influx Sql系列教程七:delete 删除数据
前面介绍了使用insert实现新增和修改记录的使用姿势,接下来我们看一下另外一个简单的使用方式,如何删除数据 1. delete 语句 delete的官方语法如下 DELETE FROM <me ...
- Why系列:谨慎使用delete
题外话 这里大家可能要笑了,这不就一个操作符吗,还用单独来讲. 有这时间,还不如去看看react源码,vue源码. 我说:react源码会去看的,但是这个也很重要. delete你了解多少 这里提几个 ...
- Esper系列(十)NamedWindow语法delete、Select+Delete、Update
On-Delete With Named Windows 功能:在Named Windows中删除事件. 格式: 1 , 4 field_b = win.field_a, 5 field_ ...
- SpringBoot系列教程JPA之指定id保存
原文链接: 191119-SpringBoot系列教程JPA之指定id保存 前几天有位小伙伴问了一个很有意思的问题,使用 JPA 保存数据时,即便我指定了主键 id,但是新插入的数据主键却是 mysq ...
- SpringBoot 系列教程 JPA 错误姿势之环境配置问题
191218-SpringBoot 系列教程 JPA 错误姿势之环境配置问题 又回到 jpa 的教程上了,这一篇源于某个简单的项目需要读写 db,本想着直接使用 jpa 会比较简单,然而悲催的是实际开 ...
- SpringBoot系列教程JPA之query使用姿势详解之基础篇
前面的几篇文章分别介绍了CURD中的增删改,接下来进入最最常见的查询篇,看一下使用jpa进行db的记录查询时,可以怎么玩 本篇将介绍一些基础的查询使用姿势,主要包括根据字段查询,and/or/in/l ...
- Influx Sql系列教程九:query数据查询基本篇二
前面一篇介绍了influxdb中基本的查询操作,在结尾处提到了如果我们希望对查询的结果进行分组,排序,分页时,应该怎么操作,接下来我们看一下上面几个场景的支持 在开始本文之前,建议先阅读上篇博文: 1 ...
- Influx Sql系列教程八:query数据查询基本篇
前面几篇介绍了InfluxDB的添加,删除修改数据,接下来进入查询篇,掌握一定的SQL知识对于理解本篇博文有更好的帮助,下面在介绍查询的基础操作的同时,也会给出InfluxSql与SQL之间的一些差别 ...
随机推荐
- 深入解析Java反射基础
博客原文:http://www.sczyh30.com/posts/Java/java-reflection-1/ - 这老哥写的特别好 一.回顾:什么是反射? 反射(Reflection)是Java ...
- Mesos源码分析(7): Mesos-Slave的启动
Mesos-Slave的启动是从src/slave/main.cpp中的main函数开始的. 看过了Mesos-Master的启动过程,Mesos-Slave的启动没有那么复杂了. 1. ...
- 【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架
第21章 RL-TCPnet之高效的事件触发框架 本章节为大家讲解高效的事件触发框架实现方法,BSD Socket编程和后面章节要讲解到的FTP.TFTP和HTTP等都非常适合使用这种方式 ...
- Java线程状态Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释
一.线程5种状态 新建状态(New) 新创建了一个线程对象. 就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获 ...
- [Swift]LeetCode221. 最大正方形 | Maximal Square
Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's and re ...
- [Swift]LeetCode552. 学生出勤记录 II | Student Attendance Record II
Given a positive integer n, return the number of all possible attendance records with length n, whic ...
- [Swift]LeetCode1006. 笨阶乘 | Clumsy Factorial
Normally, the factorial of a positive integer n is the product of all positive integers less than or ...
- 95%的技术面试必考的JVM知识点都在这,另附加分思路!
概述:知识点汇总 jvm的知识点汇总共6个大方向:内存模型.类加载机制.GC垃圾回收是比较重点的内容.性能调优部分偏重实际应用,重点突出实践能力.编译器优化和执行模式部分偏重理论基础,主要掌握知识点. ...
- Spring高级装配bean
目录 spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表达式语言 一.环境与profile 配置profile bean 在软件开发的时候,有一个 ...
- springcloud之服务注册与发现
本次分享的是关于springcloud服务注册与发现的内容,将通过分别搭建服务中心,服务注册,服务发现来说明:现在北京这边很多创业公司都开始往springcloud靠了,可能是由于文档和组件比较丰富的 ...