前言

"newmalloc()有什么区别",这是一个很常见的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[]()

因此如果没有析构处理的必要的话,就不要写析构函数了,省内存。

到这里,我们很自然的就能知道为什么newdelete以及new[]delete[]要配套使用了。

后话

newdelete是很基础的知识了,日常只需要记住配套使用,不遗漏delete就足够了。

可以想想,下面这段代码运行时会有问题么,会破坏malloc内存管理结构、内存泄漏或者踩内存么?

A* a = new A;

a->~A();
delete[] (char*)a;

C++系列总结——new和delete的更多相关文章

  1. SpringBoot系列教程JPA之delete使用姿势详解

    原文: 190702-SpringBoot系列教程JPA之delete使用姿势详解 常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿 ...

  2. Influx Sql系列教程七:delete 删除数据

    前面介绍了使用insert实现新增和修改记录的使用姿势,接下来我们看一下另外一个简单的使用方式,如何删除数据 1. delete 语句 delete的官方语法如下 DELETE FROM <me ...

  3. Why系列:谨慎使用delete

    题外话 这里大家可能要笑了,这不就一个操作符吗,还用单独来讲. 有这时间,还不如去看看react源码,vue源码. 我说:react源码会去看的,但是这个也很重要. delete你了解多少 这里提几个 ...

  4. Esper系列(十)NamedWindow语法delete、Select+Delete、Update

    On-Delete With Named Windows 功能:在Named Windows中删除事件. 格式: 1  ,   4  field_b = win.field_a,  5  field_ ...

  5. SpringBoot系列教程JPA之指定id保存

    原文链接: 191119-SpringBoot系列教程JPA之指定id保存 前几天有位小伙伴问了一个很有意思的问题,使用 JPA 保存数据时,即便我指定了主键 id,但是新插入的数据主键却是 mysq ...

  6. SpringBoot 系列教程 JPA 错误姿势之环境配置问题

    191218-SpringBoot 系列教程 JPA 错误姿势之环境配置问题 又回到 jpa 的教程上了,这一篇源于某个简单的项目需要读写 db,本想着直接使用 jpa 会比较简单,然而悲催的是实际开 ...

  7. SpringBoot系列教程JPA之query使用姿势详解之基础篇

    前面的几篇文章分别介绍了CURD中的增删改,接下来进入最最常见的查询篇,看一下使用jpa进行db的记录查询时,可以怎么玩 本篇将介绍一些基础的查询使用姿势,主要包括根据字段查询,and/or/in/l ...

  8. Influx Sql系列教程九:query数据查询基本篇二

    前面一篇介绍了influxdb中基本的查询操作,在结尾处提到了如果我们希望对查询的结果进行分组,排序,分页时,应该怎么操作,接下来我们看一下上面几个场景的支持 在开始本文之前,建议先阅读上篇博文: 1 ...

  9. Influx Sql系列教程八:query数据查询基本篇

    前面几篇介绍了InfluxDB的添加,删除修改数据,接下来进入查询篇,掌握一定的SQL知识对于理解本篇博文有更好的帮助,下面在介绍查询的基础操作的同时,也会给出InfluxSql与SQL之间的一些差别 ...

随机推荐

  1. Servlet 监听器Listner

    定义:      专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动. Servlet 规范为每种事件监听器都定义了相应的接口,它用于监听 ...

  2. 对scanf和printf的研究!!

    在做项目的时候,突然很纠结要不要清理.所以赶紧写一篇博客记一下!! 1. scanf函数 在代码中,如果碰到了两个挨着输入的情况,就会出现问题!! 输入一个字符 r 就会出现一下情况!! 第2句sca ...

  3. Java互联网架构-直播互动平台高并发分布式架构应用设计

    概述 网页HTML 静态化: 其实大家都知道网页静态化,效率最高,消耗最小的就是纯静态化的 html 页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法, ...

  4. js 生成随机炫彩背景

    在浏览 https://ghost.org/xxxx/ 时. 可以使用 background-size: cover; 加上很小的像素图,放大后实现炫彩背景效果. 使用 js canvas 随机生成小 ...

  5. FFmpeg命令行工具学习(三):媒体文件转换工具ffmpeg

    一.简述 ffmpeg是一个非常强大的工具,它可以转换任何格式的媒体文件,并且还可以用自己的AudioFilter以及VideoFilter进行处理和编辑.有了它,我们就可以对媒体文件做很多我们想做的 ...

  6. Pycharm中配置鼠标悬停快速提示方法参数

    第一步: 第二步: 演示:

  7. [Swift]LeetCode274.H指数 | H-Index

    Given an array of citations (each citation is a non-negative integer) of a researcher, write a funct ...

  8. [Swift]LeetCode684. 冗余连接 | Redundant Connection

    In this problem, a tree is an undirected graph that is connected and has no cycles. The given input ...

  9. Python内置函数(53)——repr

    英文文档: repr(object) Return a string containing a printable representation of an object. For many type ...

  10. .NET Core实战项目之CMS 第七章 设计篇-用户权限极简设计全过程

    写在前面 这篇我们对用户权限进行极简设计并保留其扩展性.首先很感谢大家的阅读,前面六章我带着大家快速入门了ASP.NET Core.ASP.NET Core的启动过程源码解析及配置文件的加载过程源码解 ...