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. 安装CPULimit到Linux(源码)

    运行环境 适用系统:所有Linux系统 当前版本:无 硬件要求:无 安装过程 1.安装依赖 root@localhost:~# apt-get -y install git 2.从GitHUB中克隆源 ...

  2. Java知识点题集

    一.红黑树的特性 (1)每个节点或者是黑色,或者是红色. (2)根节点是黑色. (3)每个叶子节点(NIL)是黑色. [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!] (4)如果一个节 ...

  3. 09 : 构造方法 & 代码块

    构造方法 概念 构造方法是一种特殊的方法,它是一个与类同名的方法 对象的创建就是通过构造方法来完成. 其功能主要是完成对象的创建或者对象的初始化 当类实例化new一个对象时会自动调用构造方法 构造方法 ...

  4. ReportViewer Win32Exception (0x80004005): 创建窗口句柄时出错

    System.ComponentModel.Win32Exception (0x80004005): 创建窗口句柄时出错. 在 System.Windows.Forms.NativeWindow.Cr ...

  5. window10安装nginx及请求转发到tomcat服务器访问项目及开机自启

    一.安装ngnix 1.  到nginx官网上下载相应的安装包,http://nginx.org/en/download.html: 下载进行解压,将解压后的文件放到自己心仪的目录下,我的解压文件放在 ...

  6. mycat linux上目录介绍

    1.本文引用来自mycat官方文档,具体可以浏览mycat官网 2.linux系统下面的目录 MyCAT 有提供编译好的安装包,支持 windows.Linux.Mac.Solaris 等系统上安装与 ...

  7. nginx配置长连接(ajax60秒请求超时)

    个人博客 地址:http://www.wenhaofan.com/article/20180911150337 1.在使用ajax做轮训的时候前台发出的ajax请求总是会在60秒之后返回405超时响应 ...

  8. Activiti工作流学习之SpringBoot整合Activiti5.22.0实现在线设计器(二)

    一.概述 网上有很多关于Eclipse.IDEA等IDE插件通过拖拽的方式来画工作流程图,个人觉得还是不够好,所以花点时间研究了一下Activiti在线设计器,并与SpringBoot整合. 二.实现 ...

  9. Oracle11g配置监听

    步骤 1.在windows系统上安装好Oracle后,点击右下角开始菜单Oracle目录下选择Net Manager进行配置,也可以使用Net Configuration Assistant(建议使用 ...

  10. Fragment应用

    使用母页和子页配合展示内容:母页和子页都有自己的activity. 母页是含有frameLayout控件的页面.子页通过配置,在frameLayout控件中显示:frameLayout本身没有任何内容 ...