c++ 是公司开发最常用的语言之一, 那new和delete 这两个函数是所有开发者即爱又恨的函数。由new 和delete引发的bug , coredump , 让多少程序员加了多少班。

1. 遇到的问题

C++ 中,你也许经常使用 new 和 delete 来动态申请和释放内存,但你可曾想过以下问题呢?

● new 和 delete 是函数吗?

● new [] 和 delete [] 又是什么? 什么时候用它们? 什么情况下等同于new和delete?

● 你知道 operator new 和 operator delete 吗?

● delete是如何知道释放内存的大小?

2. new和delete

1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

2. new 返回指定类型的指针,并且可以自动计算所需要大小; 而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。

3. malloc\free只管分配\释放内存,并不能对所得的内存进行初始化\析构,而且获取的内存数值值是随机的。

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

4. 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

3. new[] 和 delete[]

new[]和delete[]的使用看下例代码:

#include <stdio.h>
#include <memory> int iConstructor = ;
int iDestructor = ; class A
{
public:
A()
{
printf("A::Constructor: %d \n", ++iConstructor);
}
~A()
{
printf("A::Destructor: %d \n", ++iDestructor);
}
}; int main()
{
A* pA = new A[]; delete pA; // ✘ pA0~9的内存全部释放 但是只调用了pA[0]的析构 所以若A内还有动态申请内存的话会出现内存泄漏;
//free(pA); // ✘ pA0~9的内存全部释放 pA[0]~p[9]的析构都没调用 所以若A内还有动态申请内存的话会出现内存泄漏;
//delete[]pA; // ✔ 无内存泄漏,正确书写方式,标准规范;
//delete[1]pA; // ✔ 无内存泄漏,书写标准不规范而已;
//delete[10]pA; // ✔ 无内存泄漏,书写标准不规范而已;
//delete[20]pA; // ✔ 无内存泄漏,书写标准不规范而已; {
// std::shared_ptr<A> pAA(new A[10]);
// 等同于 delete pAA;
} return ;
}

 3. operator new 和 operator delete

 4. 内存释放实现机制

delete和free是如何知道应该释放多少内存呢?

如果让我们自己设计的话,我们应该会有这么两种方案:

第一,底层设置一个MemoryManager用来管理申请的内存地址和空间大小;

第二,使用申请内存前的固定区域地址存储有关内存申请信息,类似于报头和报体。

如果用第一种方案的申请、释放内存会涉及插入、查找、删除操作大大影响效率,实际上C++编译器也是用的第二种方案,下面我们就来验证一下。

如上图,pA动态申请1个A的内存空间,地址为0x00766888,内部成员变量iMember占用4个字节,数值为0x64000000(x86小端存储) 即为默认值100。前面16个字节用来管理内存申请的信息,0x04000000为4即为pA的申请内存长度。

那new[]是如何管理的呢?

如上图,pA动态申请20个A的内存空间,地址为0x00779E7C,内部成员变量iMember占用4个字节,数值为0x64000000(x86小端存储) 即为默认值100,总共80个字节,上图0x00779E7C后面连续20个A确实都是100。那我们再看下数组申请是如何定义的呢?

0x54000000为84 为动态申请内存的长度,明明只需要80个字节,为啥多分了4个呢。 我们发现多分了的4个用来存储了数组的个数,这里就是0x14000000。存储数组个数干嘛呢,就是为了遍历调用各个类对象的析构函数额,防止内存泄漏,最后再将这申请的84个字节完全释放掉。方案完美额~ (上两例说是分别申请了4、84个字节,其实堆内存实际占用了20、100个字节额 那16个作为头部用于记录作用啦)。

这里需要注意的是此处的数组个数存储只是针对自定义数据类型才会出现,因为基本数据类型不需要调用析构额,有兴趣的读者可以监视下 int* pArr = new int[20]。

这种思路就是通信协议里报文的报头和报体机制额,报头固定、报体长度可变。

new和delete的深层次剖析(C++)的更多相关文章

  1. C++ 中的 delete[] 机制剖析

    本文简单总结了delete[]放在析构函数中VS放在主函数中的区别(针对自己定义类). delete原理简单剖析(摘至https://zhidao.baidu.com/question/1540902 ...

  2. JavaScript 优雅的实现方式包含你可能不知道的知识点

    有些东西很好用,但是你未必知道:有些东西你可能用过,但是你未必知道原理. 实现一个目的有多种途径,俗话说,条条大路通罗马.很多内容来自平时的一些收集以及过往博客文章底下的精彩评论,收集整理拓展一波,发 ...

  3. 深入C++05:运算符重载

    运算符重载 1.复数类 运算符重载目的:使对象运算表现得和编译器内置类型一样: 复数类例子 #include<iostream> using namespace std; class CC ...

  4. 深度剖析malloc、free和new、delete

    1.malloc,free是C语言的函数,而new,delete是操作符,属于C++的语法,一定注意这两个不再是函数了,而是操作符. 2.malloc和new对于分配基础类型变量和数组变量,它们除了语 ...

  5. libevent源码深度剖析

    原文地址: http://blog.csdn.net/sparkliang/article/details/4957667 第一章 1,前言 Libevent是一个轻量级的开源高性能网络库,使用者众多 ...

  6. 计算机程序的思维逻辑 (30) - 剖析StringBuilder

    上节介绍了String,提到如果字符串修改操作比较频繁,应该采用StringBuilder和StringBuffer类,这两个类的方法基本是完全一样的,它们的实现代码也几乎一样,唯一的不同就在于,St ...

  7. C#进阶系列——WebApi 路由机制剖析:你准备好了吗?

    前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...

  8. MapReduce剖析笔记之二:Job提交的过程

    上一节以WordCount分析了MapReduce的基本执行流程,但并没有从框架上进行分析,这一部分工作在后续慢慢补充.这一节,先剖析一下作业提交过程. 在分析之前,我们先进行一下粗略的思考,如果要我 ...

  9. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

随机推荐

  1. Charles抓包问题

    Charles抓包问题 抓包失败解决方法之一:在proxy下的Recording Setting找到解决方法 找到里面的include 把勾去掉,点击OK,然后就恢复正常可以抓包了.

  2. Pikachu-php反序列化

    在理解这个漏洞前,你需要先搞清楚php中serialize(),unserialize()这两个函数. 序列化serialize()序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象 ...

  3. C# LINQ学习笔记五:LINQ to XML

    本笔记摘抄自:https://www.cnblogs.com/yaozhenfa/p/CSharp_Linq_For_Xml.html,记录一下学习过程以备后续查用. 一.生成xml 1.1创建简单的 ...

  4. 2019-08-25 纪中NOIP模拟A组

    T1 [JZOJ6314] Balancing Inversions 题目描述 Bessie 和 Elsie 在一个长为 2N 的布尔数组 A 上玩游戏. Bessie 的分数为 A 的前一半的逆序对 ...

  5. CR(Code Review)代码评审如何发挥作用

    在CODE中经常会发起代码评审和进行评审任务,可是根据目前的做法流程,我认为它就是走走形式,为了应付检查,根本没有达到预期的效果,即审查代码质量.学习他人写的代码和提高自身写代码的能力.对此,将从两方 ...

  6. base(根URL)

    指定用于一个文档中包含的所有相对 URL 的根 URL.一份中只能有一个 <base> 元素. 可以通过使用 document.baseURI 的 JS 脚本查询 属性 包含全局属性 hr ...

  7. 《深入理解java虚拟机》读书笔记十一——第十二章

    第十二章  Java内存模型与线程 1.硬件效率与一致性 由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cac ...

  8. SSM项目使用junit单元测试时Mybaties通配符加载Mapper不能正常加载

    个人博客 地址:http://www.wenhaofan.com/article/20181108104133 问题描述 项目使用maven build 以及tomcat run能够正常运行,但是使用 ...

  9. Docker最全教程——从理论到实战(十二)

    前言 Ubuntu是一个以桌面应用为主的开源GNU/Linux操作系统,应用很广.本篇主要讲述Ubuntu下使用SSH远程登录并安装Docker,并且提供了Docker安装的两种方式,希望对大家有所帮 ...

  10. js--javascript学习

    js -- javascript ECMAscript5 ECMAscript6 -- vue.js react .. 由三个部分组成 1 ECMAscript5的核心 js语言 2 BOM 浏览器对 ...