深入理解new[]和delete[]_new delete-程序员宅基地

技术标签: c++  c++程序设计  

        c++的动态内存管理方式和c语言不一样,在c++中使用new和delete来替换c语言中的malloc和free。这里有几个点不一样,

 

        1、new和delete是操作符,malloc和free是函数(我的理解是c++将new和delete约定为操作符而已,new和delete操作符重载函数本质上还是函数)

        2、c++有了类的概念,类对象的初始化除了要分配内存,还需要对内存进行初始化!所以,c++必须引入一种新的内存分配方式,既可以像malloc一样开辟内存,还要能够调用类对象的构造函数(delete的引入同理)。

        3、new[]和delete[]是c++完全新增的内存操作符,他们和new和delete也是有不一样的地方。

        下面,咱们来一一讲解

 

1、重载操作符

        既然new和delete都是操作符,咱们可以对new和delete进行重载;当你再使用new和delete操作内存时,编译器就会调用到咱们自己重载的new/delete全局函数了。(如对操作符重载不了解的,请自行补充知识)


void* operator new(size_t size){
    if(size == 0) size = 1;
    
    void* ptr = malloc(size);
    if(ptr == nullptr){
        std::cout << "ERROR NEW!" << std::endl;
    }
    std::cout << "NEW Memory Size = " << size << " address = " << ptr << std::endl;
    return ptr;
}

void* operator new[](size_t size){
    if(size == 0) size = 1;

    void* ptr = malloc(size);
    if(ptr == nullptr){
        std::cout << "ERROR NEW[]!" << std::endl;
    }
    std::cout << "NEW[] Memory Size = " << size << " address = " << ptr << std::endl;

    return ptr;
}
void operator delete(void* ptr){
    std::cout << "DELETE " << ptr << std::endl;
    if(ptr) free(ptr);
}
void operator delete[](void* ptr){
    std::cout << "DELETE[] " << ptr << std::endl;
    if(ptr) free(ptr);
}

        此时,再使用 int* p = new int {1}; 开辟内存,那么,c++编译器会自动链接到我们刚才的操作符重载函数 void* operator new(size_t size) ,至于编译器是怎么将 int* p = new int {1}; 解析成 void* operator new(size_t size)  函数的,咱们不关心,咱们只要知道编译器做了这样一层代码解析转换即可。并且,转换的过程中会将需要开辟的内存大小当作形式参数传递过去!

        这里还有一点需要注意:函数的返回值是void*,而咱们new操作接收的返回值为int*,这里其实也是编译在底层做了一层转换!(非常重要,new 的返回值和 operator new 函数的返回并不完全相同,包括类型和地址

2、new和delete的原理

         在开始验证new和delete之前,咱们先使用以下代码:

#include <iostream>
#include <stdlib.h>
#include <memory>
using namespace std;

//此处添加前面一步重载new和delete的代码

class Master
{
public:
    Master()
    {
        printf("constructor ---- %p\n",this);
        c = rand()%10000;
    }
    ~Master()
    {
        printf("destructor ---- %p\n", this);
    }
public:
    int c;
};
int main()
{
    Master * p = new Master{};
    delete p;
    return 0;
}

推荐一个在线调试cpp的网站:Compiler Explorer (godbolt.org)

 Master * p = new Master{}; 这一行的汇编信息如下:

  

这里有3个点要注意,

  1.  内存大小在编译时确定 
  2.  会先调用operator new(unsigned long)函数
  3.  最后会调用构造函数

那么,delete操作的汇编信息应该和new刚好相反,这个可以自行测试!

下面将咱们重载的operator方法添加进去,然后执行,output日志信息如下:

NEW Memory Size = 4 address = 0x2154eb0
constructor ---- 0x2154eb0
destructor ---- 0x2154eb0
DELETE 0x2154eb0

        通过日志,咱们也能够得出这样的结论:

  1.  new: 先malloc内存,然后执行构造函数
  2.  delete:先执行析构函数,然后free内存

 

3、new[]和delete[]的原理

         前面咱们说了new/delete与new[]/delete[]有一些不同,那我们先看看日志信息,请添加以下测试代码:

int main()
{
    Master * p = new Master{};
    
    //测试new[]
    Master * pArray = new Master[5];    
    printf("pArray = %p\n", pArray);
    printf("pArray[0] = %p\n", &pArray[0]);
    printf("pArray[1] = %p\n", &pArray[1]);

    // delete[] pArray;
    // delete p;
    return 0;
}


///

output:---------------------------------------begin

NEW Memory Size = 4 address = 0x565422925eb0
constructor ---- 0x565422925eb0
NEW[] Memory Size = 28 address = 0x5654229262e0
constructor ---- 0x5654229262e8
constructor ---- 0x5654229262ec
constructor ---- 0x5654229262f0
constructor ---- 0x5654229262f4
constructor ---- 0x5654229262f8
pArray = 0x5654229262e8
pArray[0] = 0x5654229262e8
pArray[1] = 0x5654229262ec

output:----------------------------------------end

        重点来了,malloc返回的内存地址(0x5654229262e0)和new[]返回的地址(0x5654229262e8)并不一致,而且申请的内存大小并不是Master类的大小 4*5=20,而是28!!

 

         通过汇编代码,我们看到这里确实偏移了8个字节!其他的操作与new没有多大差别,都是先调用operator new[],然后再循环调用数组中的每个对象的构造函数!

 

         通过output日志和汇编分析,可以得到我们前面讲到的结论非常重要,new 的返回值和 operator new 函数的返回并不完全相同,包括类型和地址)!

         我们调用 delete[] pArray; 删除的地址为 0x5654229262e8而真实触发 operator delete[] 的形参为 0x5654229262e0 详细日志信息如下:

        也就是operator new[]operator delete[] 的操作的内存是真实地址,并且是正确的!为什么会这样?就是因为c++编译器在底层做了一次转换!

        这个问题也很好理解,如果编译器没有这样一层逻辑转换,当你 delete[] 一个数组时,编译器要怎么操作呢?他怎么知道数组的长度然后遍历去调用析构函数呢?所以,这样一层逻辑转换有了存在的意义,这也体现在 new[] 数组时为什么大小并不是 = 对象大小*数组长度,而是多了8个字节!

        好了嘛,那多出来的8字节是不是就是数组长度呢?咱们可以把这块内存信息输出!添加以下代码:

    unsigned long* pSize = (unsigned long*)pArray - 1;//偏移8字节
    printf("p = %p\n", pSize);
    printf("sizeof pArray = %d\n", *pSize);//输出该地址上的内存数据

日志输出:

偏移8字节之后就是咱们 operator new[] 函数分配的真实的内存地址,然后咱们输出该地址8字节空间的数据信息为5,这就是申请的数据空间的大小!

也就是有了这个多余的8字节(在32位系统上为4字节),new[] 存储数组长度到该内存, delete[] 时就能够从该内存读取数组长度,并遍历数组去调用析构函数啦!

        好,至此,咱们就理解了c++中的 new/delete 以及 new[]/delete[]  到底干了啥!

        最后,大家思考以下,new[] 创建的数组,你能遍历去调用对象析构函数+free内存吗?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhaxun/article/details/120139067

智能推荐

Windows添加右键在此处打开命令行_右键 在windows终端打开-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏2次。一键自动导入设置。将以下内容保存成reg文件,如a.reg,双击该文件自动导入设置。Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\Directory\Background\shell\OpenCMDHere]"ShowBasedOnVelocityId"=dword:00639bc8[HKEY_CLASSES_ROOT\..._右键 在windows终端打开

Mac 如何干净的卸载 VMware Fusion_macbook中vmware fusion怎么卸载-程序员宅基地

文章浏览阅读9.1k次。# 1. 删除根目录下的,需要用管理员权限sudo rm -rf /Applications/VMware\ Fusion.appsudo rm -rf /Library/Application\ Support/VMwaresudo rm -rf /Library/Preferences/VMware\ Fusionsudo rm -rf /Library/Logs/VMware/s..._macbook中vmware fusion怎么卸载

js用递归遍历多维数组_JS数组的遍历上 (含forEach等方法源码)-程序员宅基地

文章浏览阅读3.5k次。forEach()方法用于调用数组的每个元素(循环遍历数组中的每一个元素),并将元素传递给回调函数。它内部的回调函数可以传入三个参数:function(item, index, arr)item为必填参数,表示当前元素index为可选参数,表示当前元素的索引arr同样为可选参数,表示当前元素所属的数组对象(正在遍历的这个数组)。forEach源码实现:Array.prototype.myForEa..._js递归遍历多维数组

android的apk在oppo手机无法安装(安装包没有签名文件)-程序员宅基地

文章浏览阅读2.6k次。在华为手机可以安装,却在oppo手机无法安装,这是怎么回事呢?原来在打包问题上之前仅仅只勾选了第二个,现在把两个都勾上,然后打包安装到oppo手机,完美解决!========================================Talk is cheap, show me the code============================..._oppo安装不了apk文件

什么才是物联网领域最好的开发语言?_micropython和c++执行效率对比-程序员宅基地

文章浏览阅读5k次。采用C/C++语言,运行最快,一般采用厂家提供的底层驱动支持包BSP,所有MCU都支持。最近很多小伙伴找我,说想要一些物联网学习资料,然后我根据自己从业十年经验,熬夜搞了几个通宵,精心整理了一份「物联网入门到高级教程+工具包」,点个关注,全部无偿共享给大家!采用uLISP语言,利用神奇的LISP语言,函数式编程,开发效率高,运行效率也较好。采用microPython语言,软件开发效率可提高5倍以上,但运行效率一般,有时需要优化,容易学习,需要选择microPython支持的MCU。_micropython和c++执行效率对比

自适应滤波(LMS,RLS)_利用lms与rls提取电频信号-程序员宅基地

文章浏览阅读3.6w次,点赞19次,收藏156次。1.背景及相关知识介绍自适应滤波存在于信号处理、控制、图像处理等许多不同领域,它是一种智能更有针对性的滤波方法,通常用于去噪。图中x(j)表示 j 时刻的输入信号值,y(j)表示 j 时刻的输出信号值,d(j)表示 j 的参考信号值或所期望响应信号值,误差信号e(j)为d(j)与y(j)之差。自适应数字滤波器的滤波参数受误差信号e(j)的控制,根据e(j)的值而自动调整,使之适_利用lms与rls提取电频信号

随便推点

Tensorflow下用自己的数据集对Faster RCNN进行训练和测试(二)-程序员宅基地

文章浏览阅读2.5w次,点赞3次,收藏127次。对于Tensorflow版本的Faster RCNN网络,网上包括github上都有不同的源码版本,本人之前也在进行不同版本的运行测试,可以说是每个版本都有不同的错误,在解决这些错误时可谓道阻且长。而对于用自己的数据集来训练和测试Faster RCNN网络,本人在之前的博客https://blog.csdn.net/hitzijiyingcai/article/details/81808091中已...

jq 穿梭框_jq穿梭框-程序员宅基地

文章浏览阅读1.1k次。#HTML代码&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;head&gt;&am_jq穿梭框

vue-cli的工程如何正确使用Google Analytics?-程序员宅基地

文章浏览阅读590次。前言最方便的方法,莫过于使用vue-analytics:https://github.com/MatteoGabriele/vue-analytics。包是有了,可是真正使用起来会发现Google Analytics(下称GA)可能没检测到或者出现漏统计的问题。本文的主题,就是讨论几个基本的检查点和说明下GA的基本用法,确保GA正常工作。本文分为以下几部分:如何正确地初始化G..._"cli\": { \"analytics\": false,"

Hybrd A*(混合A*)算法_混合a*算法-程序员宅基地

文章浏览阅读1.5w次,点赞34次,收藏282次。Hybrid A*算法是一种图搜索算法,改进于A*算法。与普通的A*算法区别在于,Hybrid A*规划的路径考虑了车辆的运动学约束,即满足了车辆的最大曲率约束。_混合a*算法

权限系统设计--zTree的分析简化使用_如何利用结构树初始化权限设置-程序员宅基地

文章浏览阅读191次。权限系统设计与开发什么是权限系统?权限系统是一种设定用户与可操作模块之间关系的系统。通过设定用户与可操作的模块之间的关系,控制用户在可指定范围内进行业务执行基于用户的权限控制(UBAC:User-BasedAccessControl)基于角色的权限控制(RBAC:role-BasedAccessControl)角色的权限控制RBAC树形控件结构分析(1)树形结构如下图所示:对应的实现技术有:dTreetdTreezTree我们主要来看关于zTree的相关操作.我们主要是针对_如何利用结构树初始化权限设置

饿了么element滚动条elScrollbar的使用(包含在el-table中的使用)_el-table中使用el-scrollbar-程序员宅基地

文章浏览阅读1w次,点赞3次,收藏23次。饿了么element滚动条elScrollbar的使用(包含在el-table中的使用)简介引用基本使用只显示横向滚动条只显示纵向滚动条在el-table中使用滚动条简介由于项目需要统一滚动条样式有需要兼容ie,之前有用过一款滚动条插件,但是有点儿卡,而且数据更新需要自己重置,又刚好在elment官网看到了饿了么自带的滚动条样式比较友好,就想把他扣下来,搜索之后原来element-ui有这个滚动条的组件,只是文档里面没有,经使用验证可以兼容到ie10,而且可以很好的DIY样式,更不用每次更新数据就需要重_el-table中使用el-scrollbar

推荐文章

热门文章

相关标签