初探C++ 中的 new 和 delete

C++中使用指针 new申请内存、创建数组、传递 C++中使用指针new申请内存,创建数组,传递 阅读详情

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

  • new 和 delete 是函数吗?
  • new [] 和 delete [] 又是什么?什么时候用它们?
  • 你知道 operator new 和 operator delete 吗?
  • 为什么 new [] 出来的数组有时可以用 delete 释放有时又不行?
  • …

如果你对这些问题都有疑问的话,不妨看看我这篇文章。

new 和 delete 到底是什么?

如果找工作的同学看一些面试的书,我相信都会遇到这样的题:sizeof 不是函数,然后举出一堆的理由来证明 sizeof 不是函数。在这里,和 sizeof 类似,new 和 delete 也不是函数,它们都是 C++ 定义的关键字,通过特定的语法可以组成表达式。和 sizeof 不同的是,sizeof 在编译时候就可以确定其返回值,new 和 delete 背后的机制则比较复杂。
继续往下之前,请你想想你认为 new 应该要做些什么?也许你第一反应是,new 不就和 C 语言中的 malloc 函数一样嘛,就用来动态申请空间的。你答对了一半,看看下面语句:

string *ps = new string("hello world");

你就可以看出 new 和 malloc 还是有点不同的,malloc 申请完空间之后不会对内存进行必要的初始化,而 new 可以。所以 new expression 背后要做的事情不是你想象的那么简单。在我用实例来解释 new 背后的机制之前,你需要知道 operator new 和 operator delete 是什么玩意。

operator new 和 operator delete

这两个其实是 C++ 语言标准库的库函数,原型分别如下:

void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array

后面两个你可以先不看,后面再介绍。前面两个均是 C++ 标准库函数,你可能会觉得这是函数吗?请不要怀疑,这就是函数!C++ Primer 一书上说这不是重载 new 和 delete 表达式(如 operator= 就是重载 = 操作符),因为 new 和 delete 是不允许重载的。但我还没搞清楚为什么要用 operator new 和 operator delete 来命名,比较费解。我们只要知道它们的意思就可以了,这两个函数和 C 语言中的 malloc 和 free 函数有点像了,都是用来申请和释放内存的,并且 operator new 申请内存之后不对内存进行初始化,直接返回申请内存的指针。

我们可以直接在我们的程序中使用这几个函数。

new 和 delete 背后机制

知道上面两个函数之后,我们用一个实例来解释 new 和 delete 背后的机制:

我们不用简单的 C++ 内置类型来举例,使用复杂一点的类类型,定义一个类 A:

class A
{
public:
    A(int v) : var(v)
    {
        fopen_s(&file, "test", "r");
    }
    ~A()
    {
        fclose(file);
    }

private:
    int var;
    FILE *file;
};

很简单,类 A 中有两个私有成员,有一个构造函数和一个析构函数,构造函数中初始化私有变量 var 以及打开一个文件,析构函数关闭打开的文件。

我们使用

class *pA = new A(10);

来创建一个类的对象,返回其指针 pA。如下图所示 new 背后完成的工作:

简单总结一下:

  1. 首先需要调用上面提到的 operator new 标准库函数,传入的参数为 class A 的大小,这里为 8 个字节,至于为什么是 8 个字节,你可以看看《深入 C++ 对象模型》一书,这里不做多解释。这样函数返回的是分配内存的起始地址,这里假设是 0x007da290。
  2. 上面分配的内存是未初始化的,也是未类型化的,第二步就在这一块原始的内存上对类对象进行初始化,调用的是相应的构造函数,这里是调用 A:A(10); 这个函数,从图中也可以看到对这块申请的内存进行了初始化,var=10, file 指向打开的文件。
  3. 最后一步就是返回新分配并构造好的对象的指针,这里 pA 就指向 0x007da290 这块内存,pA 的类型为类 A 对象的指针。

所有这三步,你都可以通过反汇编找到相应的汇编代码,在这里我就不列出了。

好了,那么 delete 都干了什么呢?还是接着上面的例子,如果这时想释放掉申请的类的对象怎么办?当然我们可以使用下面的语句来完成:

delete pA;

delete 所做的事情如下图所示:

delete 就做了两件事情:

  1. 调用 pA 指向对象的析构函数,对打开的文件进行关闭。
  2. 通过上面提到的标准库函数 operator delete 来释放该对象的内存,传入函数的参数为 pA 的值,也就是 0x007d290。

好了,解释完了 new 和 delete 背后所做的事情了,是不是觉得也很简单?不就多了一个构造函数和析构函数的调用嘛。

如何申请和释放一个数组?

我们经常要用到动态分配一个数组,也许是这样的:

string *psa = new string[10];      //array of 10 empty strings
int *pia = new int[10];           //array of 10 uninitialized ints

上面在申请一个数组时都用到了 new [] 这个表达式来完成,按照我们上面讲到的 new 和 delete 知识,第一个数组是 string 类型,分配了保存对象的内存空间之后,将调用 string 类型的默认构造函数依次初始化数组中每个元素;第二个是申请具有内置类型的数组,分配了存储 10 个 int 对象的内存空间,但并没有初始化。

如果我们想释放空间了,可以用下面两条语句:

delete [] psa;
delete [] pia;

都用到 delete [] 表达式,注意这地方的 [] 一般情况下不能漏掉!我们也可以想象这两个语句分别干了什么:第一个对 10 个 string 对象分别调用析构函数,然后再释放掉为对象分配的所有内存空间;第二个因为是内置类型不存在析构函数,直接释放为 10 个 int 型分配的所有内存空间。

这里对于第一种情况就有一个问题了:我们如何知道 psa 指向对象的数组的大小?怎么知道调用几次析构函数?

这个问题直接导致我们需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

还是用图来说明比较清楚,我们定义了一个类 A,但不具体描述类的内容,这个类中有显示的构造函数、析构函数等。那么 当我们调用

class A *pAa = new A[3];

时需要做的事情如下:

从这个图中我们可以看到申请时在数组对象的上面还多分配了 4 个字节用来保存数组的大小,但是最终返回的是对象数组的指针,而不是所有分配空间的起始地址。

这样的话,释放就很简单了:

delete [] pAa;

这里要注意的两点是:

  • 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
  • 传入 operator delete[] 函数的参数不是数组对象的指针 pAa,而是 pAa 的值减 4。

为什么 new/delete 、new []/delete[] 要配对使用?

其实说了这么多,还没到我写这篇文章的最原始意图。从上面解释的你应该懂了 new/delete、new[]/delete[] 的工作原理了,因为它们之间有差别,所以需要配对使用。但偏偏问题不是这么简单,这也是我遇到的问题,如下这段代码:

int *pia = new int[10];
delete []pia;

这肯定是没问题的,但如果把 delete []pia; 换成 delete pia; 的话,会出问题吗?

这就涉及到上面一节没提到的问题了。上面我提到了在 new [] 时多分配 4 个字节的缘由,因为析构时需要知道数组的大小,但如果不调用析构函数呢(如内置类型,这里的 int 数组)?我们在 new [] 时就没必要多分配那 4 个字节, delete [] 时直接到第二步释放为 int 数组分配的空间。如果这里使用 delete pia;那么将会调用 operator delete 函数,传入的参数是分配给数组的起始地址,所做的事情就是释放掉这块内存空间。不存在问题的。

这里说的使用 new [] 用 delete 来释放对象的提前是:对象的类型是内置类型或者是无自定义的析构函数的类类型!

我们看看如果是带有自定义析构函数的类类型,用 new [] 来创建类对象数组,而用 delete 来释放会发生什么?用上面的例子来说明:

class A *pAa = new class A[3];
delete pAa;

那么 delete pAa; 做了两件事:

  • 调用一次 pAa 指向的对象的析构函数;
  • 调用 operator delete(pAa); 释放内存。

显然,这里只对数组的第一个类对象调用了析构函数,后面的两个对象均没调用析构函数,如果类对象中申请了大量的内存需要在析构函数中释放,而你却在销毁数组对象时少调用了析构函数,这会造成内存泄漏。

上面的问题你如果说没关系的话,那么第二点就是致命的了!直接释放 pAa 指向的内存空间,这个总是会造成严重的段错误,程序必然会奔溃!因为分配的空间的起始地址是 pAa 指向的地方减去 4 个字节的地方。你应该传入参数设为那个地址!

同理,你可以分析如果使用 new 来分配,用 delete [] 来释放会出现什么问题?是不是总会导致程序错误?

总的来说,记住一点即可:new/delete、new[]/delete[] 要配套使用总是没错的!

C++基础 — new的使用 new的使用 阅读详情

相关推荐

C++中new[…]和动态数组

C++中new[…]和动态数组

qq_41317716的博客 901

C++ 动态数组(new)

原文链接:动态数组(new) 一、动态数组的定义 数组的维度可以为常量、也可以为一个函数的返回值 int size() { return 3; } const int length = 3; int *p1 = new int[size()]{1,2,3}; int *p2 = new int[length]; int *p3 = new int[3]; 使用typedef为数组类型取别名 typedef int arrT[42]; int *p=new arrT; //分配一个大小

baidu_41388533的博客 5万+

深入浅出C++(四):C++ 中的新成员(new、delete)、新型的类型转换、经典问题解析

一、C++ 中的新成员 1、动态内存分配 实例分析:C+ +中的动态内存分配 编程实验:初始化动态内存 2、C++中的命名空间 实例分析:命令空间的使用 3、小结 二、新型的类型转换 1、强制类型转换 编程实验:粗暴的类型转换 2、新式类型转换 实例分析:新式类型转化初探 3、小结 三、经典问题解析一 1、关于const的疑问 编程实验:const典型问题分析 2、关于引用的疑问 编程实验:引用典型问题分析 3、小结

Exp.Joker 608

C++用new创建动态数组

如果通过像下面这样一般的方法来声明数组,则在程序编译的时候为其分配内存,不管程序最终是否用到这个数组,数组都在那里并占用内存,即静态联编。但是如果使用new,则在运行阶段需要它才进行创建,不需要则不创建,这叫动态联编,这种创建的数组叫做动态数组。并且静态联编必须在写程序时就声明数组长度,而动态联编在运行时才确定数组的长度。我们创建了一个动态数组指针p3后,我们可以通过p3[i]来直接访问数组中的第i个元素。上述代码使得声明一个长为3,类型为double的动态数组。并且动态分配一个空数组是合法的!...

qq_55621259的博客 9511

咱们一起学C++ 第一百八十一篇:之C++实践巩固与动态对象创建初探

大家好!在C++的学习过程中,实践练习是巩固知识的重要环节。同时,了解动态对象创建的相关知识,能让我们编写出更具灵活性和适应性的程序。今天,咱们就一起深入学习这些内容,希望大家都能有所收获,在C++的学习道路上共同进步!

wj_rdk的博客 1587

c++对象的动态建立和释放

1 new和delete基本语法 1)在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。 注意: new和delete是运算符,不是函数,因此执行效率高。 2)虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。new运算符的例子:

希望我的博客,能帮上你解决学习中工作中所遇到的问题 605

[CPP] new delete

前言 今天我们来谈一谈CPP中很关键的两个操作 new 和 delete ,关于他们与析构函数的关系、动态分配的内存空间、new array ( new[] ) 和 delete array ( delete[] ) 配套出现的原因以及我们可能产生误解的内存泄漏的形式。 与构造析构函数的羁绊 在一般 CPP 书中,作者都会告诉读者在调用 new 来动态创建一个对象时,会先分配空间,再调用构造函数。...

weixin_30394981的博客 316

计算机补码两位乘法移位补0还是1,补码两位乘.doc

补码两位乘沈阳航空航天大学课 程 设 计 报 告课程设计名称:计算机组成原理课程设计课程设计题目:COP2000实现补码两位乘法院(系):计算机学院专 业:计算机科学与技术(嵌入式系统方向)班 级学 号:2009040101181姓 名:张驰指导教师:杨华完成日期:2012年1月14日目 录第1章 总体设计方案11.1设计原理11.2设计思路11.3设计环境2第2章...

weixin_34471838的博客 967

c/c++ new delete初探

new delete初探 1,new有2个作用 开辟内存空间。 调用构造函数。 2,delete也有2个作用 释放内存空间 调用析构函数。 如果用new开辟一个类的对象的数组,这个类里必须有默认(没有参数的构造函数,或者有默认值的参数的构造函数)的构造函数。 释放数组时,必须加[]。delete []p 也可以用malloc,free,但是malloc不会自动调用类的构造函数,free也不...

weixin_30868855的博客 137

GD32F4系列Keil工程模板搭建全攻略(手把手教学版)

本文提供了一份详细的GD32F4系列Keil工程模板搭建指南。从软件安装、固件库获取、目录规划,到Keil工程创建、文件添加、关键配置(如编译器选项、头文件路径、时钟设置),再到代码定制与常见问题排查,手把手教你构建一个标准化、模块化的开发基础。文章重点介绍了如何利用官方固件库和代码模板,快速建立稳定高效的开发环境,适用于GD32F407等Cortex-M4内核芯片的入门与项目开发。

weixin_29159995的博客 429

【原创理论】Stochastic Coupled Dyadic System (SCDS):一个用于两性关系动力学建模的随机耦合系统框架

基于二人爱情耦合系统动力学、稳定性与最优策略的综合性理论的数值模拟与分析

weixin_44695861的博客 1134

009、C++核心特性:引用、动态内存管理(new/delete)初探

本文介绍了C++的两个核心特性:引用和动态内存管理。引用本质是变量的别名,在函数传参和返回值优化中非常实用,但需注意生命周期问题。new/delete用于手动内存管理,常见陷阱包括内存泄漏、重复释放和数组释放错误。作者建议优先使用栈内存,必须使用堆内存时遵循"谁申请谁释放"原则,并推荐现代C++工具如vector和智能指针。调试内存问题时可用valgrind等工具,养成delete后立即置空的好习惯。文章强调引用更安全,指针更灵活,并预告下期将介绍智能指针这一现代内存管理方案。

z534437748的博客 328

ADS 使用教程(一)Workspace, Library and Cell

简要介绍ADS的基本内容

Dirge19的博客 6613

c++初探

c语言分区 栈Stack(大地址,由高向低使用) 堆Heap(由低向高使用) 全局or静态常量区 文本and代码区(小地址) #include<stdlib.h> #include<stdio.h> int total = 0; //全局or静态常量区 void hehe() { //函数,在stack中 static int he = 0; //全局or静态常量区,因为static只会创建一次 he++; total++; } int main(

Panda325的博客 273

IPGUARD客户端与应用程序冲突

客户端与应用程序冲突 症状 安装客户端之后,客户端的某些程序无法运行或使用过程中出现异常,卸载客户端后恢复正常。 原因分析 可能是客户端的某些功能与应用程序发生冲突导致。 解决方法 当出现这样的情况时,可以按照以下步骤确认。 步骤1:屏蔽主动注入 在客户端配置策略中,增加配置策略hookapi_disinj,值为1;待策略同步到客户端后,重启客户端确认问题是否解决。如果解决,则转到...

IP-Guard专栏 1410

DICOM图像知识:深入详解DICOM的层级关系

解析DICOM的四个主要层级:Patient(患者)、Study(检查)、Series(序列)和SOP(服务-对象对),探讨它们之间的关系、各自的唯一标识符以及在实际应用中的用途

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 3304

侯捷C++手把手教学:new delete初探

new、delete作为表达式,他们的行为不能改变,不能重载。分解后的函数(new需要三步,delete需要两步)可以重载。 ::代表全局。这里要注意范围的问题:若类重载了new与delete,则使用重载了的,否则使用全局的new与delete。 编译器传进size_t参数的值,给编译器调用。optional代表选择,写不写这个参数无所谓。 第二个多了一个虚函数,有了虚函数就会多一个指针,sizeof值为16。 每块内存最上面是一个计数器,指明个数。如size:4表示内存大小为4,但是值为5,代表有

weixin_39770712的博客 347

利用autojs制作抢购支付宝消费劵的手机脚本

前期准备工作 1.打开支付宝,点击中间的抢消费券的广告 2.点击左上角,将抢购页面添加到首页 3.查看支付宝首页是否出现消费劵 . autojs代码 "auto"; functiontaskstart(){ sleep(1000); launchApp('支付宝'); toast("正在打开支付宝应用"); sleep(1000); varxf=textContains("消费").findOne().bounds(); ...

洛阳泰山的博客 1万+

C++内存管理初探

C/C++中程序中内存区域简单划分及介绍 内核空间:存放内核代码和数据的空间,提供给内核使用 栈(堆栈):存储非静态局部变量/实例化后的形参/返回值等等数据的空间,栈向下增长(即从高地址处向低地址处生长) 内存映射段:用于高效映射I/O映射,装载共享动态库的空间。用户可使用系统接口创建共享内存,做进程间通信 堆:在程序运行时用于动态分配的空间,堆向上增长 数据段:存储全局数据与静态(static)数据,程序结束时由系统释放 代码段:可执行的代码/只读常量 ...

qq_39208237的博客 1215

Sigrity System Explorer Transient Analysis模式进行瞬态仿真分析操作指导-单端信号

Sigrity System Explorer Transient Analysis模式可以用于瞬态仿真分析,通过搭建简易拓扑用于前仿真分析通道的时域性能,下面搭建一个简易的单端拓扑进行说明,以下图为例,准备好发送接收端的IBIS模型, (ibis_se.ibs,本例发送接收端都使用相同的IBIS模型),以及传输线的S参数模型(welement_se.sp)最上方的图形界面是传输线的模型,本例是一个上下都有参考平面的带状线,右侧的梯形是蚀刻角度,根据W1和W2以及铜厚来确定。

weixin_54787054的博客 417

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

C++模板初阶与STL初探

摘要: 本文系统讲解了C++内存管理、模板与STL核心知识。首先剖析了C/C++程序内存区域划分(栈/堆/静态区等)及各区域特性。随后对比了malloc/free与new/delete的六大关键区别,包括初始化方式、失败处理机制及对自定义类型的支持差异。在模板部分,详细介绍了函数模板的隐式/显式实例化机制和类模板的定义方法,重点演示了动态数组模板的扩容实现。最后概述了STL六大组件(容器/算法/迭代器等)及其作为标准化软件框架的价值。全文通过代码示例与原理图示,为C++泛型编程和标准库使用奠定基础。

日拱一卒,功不唐捐 220

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

C++ 内存空间初探

C++中一般将内存空间分为5个区域: 栈:函数内的局部变量存放点,由编译器自动分配和释放 堆:程序员malloc/new分配,用free/delete释放。系统最后会回收 静态存储区:存放全局变量和静态变量static。程序结束后由系统释放 常量存储区:存储常量 程序代码区:存储程序代码的区域 堆vs栈: 栈: 空间有限。普通的定义int a = 4; 就是在栈中,分配速度快,但是不能控制内存...

weixin_43912806的博客 394

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

C++学习34:内存管理初探

1 C++应用程序 从上到下就是我们对于内存的“底层”程度。而对于程序员而言,malloc就可以视为最底层,所有有关内存管理的内容,归根结底都是运用了malloc。 对于我们而言,需要掌握的内存工具如下: 下面有个应用了以上四个内容的例子: 上图的1-2行、4-5行、7-8行,实际上都是执行了相同的操作:分配一块内存,然后释放掉这块内存。(7-8行的::operator new底层就是调用的...

时间有泪 308

C++初探 4-3(复合类型 - 指针与存储)

指针与自由存储空间 指针策略 声明和初始化指针 指针的危险 指针和数字 使用new来分配内存 使用delete释放内存 使用new创建动态数组 1. 使用new创建动态数组 2. 使用动态数组 指针、数组和指针算术 指针与字符串 使用new创建动态结构 自动存储、静态存储和动态存储 1. 自动存储 2. 静态存储 3. 动态存储

w_pab的博客 645

malloc/free 与new/delete的区别

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。 1.malloc函数初探 首先我们要知道malloc是一个函数,malloc的全称是memory allocation,中文叫动态内存分配。它的原型是: void *malloc(int size);1 说明:malloc 向系统申请分配指定s

liuaaawen的博客 305

C++入门解惑(3)——初探指针(下)

2.形形色色的指针All Kinds of Pointers 前一章我们引入了指针及其定义,这一节我们继续研究各种不同的指针及其定义方式(注:由于函数指针较为特殊,本章暂不作讨论,但凡出现“指针”一词,如非特别说明均指数据指针)。1)指向指针的指针我们已经知道,指针变量是用于储存特定数据类型地址的变量,假如我们定义int *pInt;那么,pInt为一个指向整型变量的指

邝圣凯 3070
上一篇: 透过豆瓣上一篇“如何快速学习 Ruby on Rails?”,思考如何去学习新的技术
下一篇: 怎样判断自己掌握了学到的新知识
tham_
博客等级 码龄12年 233粉丝 105原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值