初探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

CPRI原理及应用--基本原理

本文主要介绍CPRI的基本原理,包括CPRI的发展历程、CPRI基本协议、速率、帧结构、时延模型等

u014035968的博客 2万+

c/c++ new delete初探

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

weixin_30868855的博客 137

Plant Simulation新手入门:从零开始搭建工厂布局仿真模型(附完整代码)

本文为Plant Simulation新手提供了一个从零开始搭建工厂布局仿真模型的完整实战教程。通过一个玩具工厂车间布局优化的具体案例,详细讲解了从数据准备、模型构建、核心逻辑编程到连接遗传算法进行自动优化的全流程,并附有完整代码示例,帮助读者快速掌握这一工业仿真软件的核心应用。

metal的博客 553

【乐企文件生成工程】添加数据库、SpringEvent事件支持

更新!1、乐企版式文件生成工程添加数据库支持2、乐企版式文件生成工程添加事件机制,异步处理文件生成,提高系统并发能力。

九七的博客 336

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

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

z534437748的博客 328

[AI] 本地部署 Dify 遇到 Internal Error?一文彻底解决密码不一致引发的问题

摘要:Dify本地部署常见InternalError错误主要由配置残留或密码不一致导致,特别是修改.env文件后未同步清除数据库记录。彻底解决方案包括:1)清理Docker数据;2)删除旧配置和数据库;3)还原源代码;4)重建.env文件;5)重新初始化部署。关键提醒:记录部署路径、备份.env文件并妥善保存密码。该错误本质是配置冲突,最有效方法是完全重置而非调试,保持清晰的部署管理可避免多数InternalError问题。(149字)

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 435

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

ADRC自抗扰算法优化(PLC梯形图篇)

ADRC自抗扰算法PLC梯形图完整源代码请参看下面博客文章:ADRC自抗扰控制算法(含梯形图完整源代码和算法公式)_adrc算法_RXXW_Dor的博客-优快云博客PLC的自抗扰控制(ADRC)算法_RXXW_Dor的博客-优快云博客_adrc算法1、自抗扰控制算法,网上很多文章有所讲解,大家也可以关注韩京清教授的论文和书籍,这里就不赘述了,下面给出梯形图+SCL代码。

RXXW_Dor的博客 2282

用Excel或WPS做两独立样本的T检验

一般情况下收集数据时常用的是Excel或者WPS,但统计时却用SPSS等软件进行统计学分析,经常很多人找不到SPSS的安装包,或者苦于正版激活或忘记教程,其实Excel或WPS自带函数就足够了,均数、标准差以及统计检验都是一个函数而已。解释:先做F检验,如果F>0.05就做T检验,Ttset里的数据为两组的数据的比较,结果如下。A组的均数计算 =AVERAGE(B2:B6)A组的标准差计算 =STDEV(B2:B6)B组的标准差计算=STDEV(B7:B19)分组 数值 A组均数 A组标准差 p值。

Mrrunsen的博客 6752

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

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

weixin_39770712的博客 347

【100分- 求最多可以派出多少支团队】(题目+思路+Java&C++&Python解析+在线测试)

用数组代表每个人的能力,一个比赛活动要求参赛团队的最低能力值为N,每个团队可以由1人或者2人组成,且1个人只能参加1个团队,计算出最多可以派出多少只符合要求的团队。

m0_73659489的博客 313

C++内存管理初探

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

qq_39208237的博客 1215

【Proteus仿真】51单片机+DAC0832+数码管制作0-25V电压可调

【Proteus仿真】51单片机+DAC0832+数码管制作0-25V电压可调

perseverance51博客 2117

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C++模板初阶与STL初探

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

日拱一卒,功不唐捐 220

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

C++ 内存空间初探

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

weixin_43912806的博客 394

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

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、付费专栏及课程。

余额充值