当前位置: 首页 > news >正文

浅谈C++的new和delete

文章目录

  • 前言
  • 1. 数据的存储方式
  • 2. C语言的管理方式
  • 3. C++的管理方式
    • 3.1 new 和 delete
    • 3.2 new 和 delete[ ]
  • 4. new和delete底层
  • 5. 对比new和malloc区别

前言

这里介绍new作为堆区内存开辟的操作符的用法和细节

我们知道C语言管理内存的函数是:malloccallocreallocfree。C语言是面向过程的编程语言;而C++是面向对象的编程语言。这就注定了C++用不惯C语言的那一套动态内存管理的方式,于是C++引入了两个操作符newdelete。这还是为了更好地处理自定义类型。所以现在我们就会从几个方面来讨论这些内存管理方式:

  • 程序中的数据类型和内存分布
  • C语言的管理方式:简要介绍malloccallocreallocfree
  • C++的处理方式及语法
  • newdelete的底层
  • mallocnew的区别

1. 数据的存储方式

在一个程序中,数据类型大概可以分为如下部分:

  1. 局部数据
  2. 全局数据和静态数据
  3. 常量数据
  4. 动态数据
  5. ……

大约有如上的数据是在内存中被存储的,那么对于内存来说,他应该如何有效的管理这些数据呢?应该如何把这些数据合理的存放呢?现在我们就需要来了解,C/C++中程序内存区域的划分
在这里插入图片描述

  • :又叫做堆栈,其主要作用就是用来存储局部变量,是一种即用即销毁的区域。存储的比如是:非静态局部变量、函数参数、返回值……栈区的运用习惯是先使用高地址后使用低地址。栈区是向下生长的
  • :程序运行的时候用于动态内存开辟的。堆区是向上生长的。
  • 数据段:存储全局数据和静态数据
  • 代码段:可执行的代码和只读的常量(例如:常量字符串)

先来看这样一个程序:

#include<stdio.h>
#include<stdlib.h>int globalVar = 1;
static int staticGlobalVar = 1;
int main()
{static int staticVar = 1;int localVar = 1;int num1[10] = {1,2,3,4};char char2[] = "abcd";const char* pChar3 = "abcd";int* ptr1 = (int*)malloc(sizeof(int)* 4);int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);return 0;
}
  • 来看下面的问题和选项:

在这里插入图片描述

2. C语言的管理方式

  • malloc

    • 在堆区上申请一块空间,返回这片空间的起始地址的指针void*类型)。
  • calloc

    • 函数原型和malloc大有不同,主要区别是:它会将开辟好的空间,每字节初始化为0
  • realloc

    • 用于修改已有的内存大小,有两种方式:原地扩容和异地扩容

对于C语言开辟的内存,需要使用函数free进行释放,不然就会造成内存泄露。同时关于malloccallocrealloc的使用还是来举个例子:

#include<stdio.h>
#include<stdlib.h>int main()
{int* ptr1 = (int*)malloc(sizeof(int)*4);int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 8);free(ptr1);free(ptr3); //不要尝试free ptr2,因为有可能发生了异地扩容,ptr2就是野指针return 0;
}

对于如上的三个函数来说,如果开辟空间失败,那么就会返回一个空指针NULL。所以有些编译器需要我们检查返回的指针是否是空指针(例如VS2022)。

3. C++的管理方式

3.1 new 和 delete

特别需要注意的是:`new`和`delete`是操作符,不是函数!

newdelete用于动态申请单个数据类型的空间。不用强制类型转换,同时还可以初始化。语法如下
例3.1:

#include<iostream>
using namespace std;int main()
{int* ptr1 = new int(1); //初始化为1int b = 3;int* ptr2 = new int(b); //用b来初始化delete ptr1;delete ptr2; //一定要记得释放return 0;
}

基本语法如下:

//   new 类型 (初始化内容)
//  delete 指针

这里我们可以看到newmalloc内置类型上似乎没有太大区别,不就是多了一个初始化吗?

  • 更重要的是,在前言部分已经提到了:C++是面向对象的开发语言,那么new的优势更体现在自定义类型,即自定义类型上。

例3.2:有如下一个A

#include<iostream>
using namespace std;class A
{
public:A(int a = 0):_a(a){cout << "A(int a) -- 构造" << endl;}A(const A& val):_a(val._a){cout << "A(const A& val) -- 深拷贝" << endl;}
private:
int _a;
};int main()
{A* ptr1 = new A(1);cout << "========" << endl;A aa(12);cout << "========" << endl;A* ptr2 = new A(aa);delete ptr1;delete ptr2;return 0;
}

运行结果是什么呢?

在这里插入图片描述
一个执行构造,另一个执行拷贝构造,很类似于在栈区进行对象的创建(意味着如果没有默认构造函数,就一定需要传参)。如果你传入的是一个右值的话,编译器可能会优化;当然也可能为你进行移动构造

在这里想要表达的是:new会根据你传入的初始化内容,调用对应的构造函数:(构造函数、拷贝构造函数、移动构造函数……),当然,这个结果是存在优化的(当同一行代码存在多次构造或者拷贝构造编译器会进行优化)。所以我们在这里可以看到,newmalloc差距是很明显的,因为new可以完成自定义类型的初始化工作,而malloc是无法完成的。

3.2 new 和 delete[ ]

在这里出现了[],我们当然很容易想到数组

new除了进行单个对象的申请空间,还可以进行像malloc那样的申请连续堆区空间并且返回这段空间的起始地址的指针
例3.3:

#include<iostream>
using namespace std;int main()
{int* ptr = new int[10]{1, 2, 2}; //继承了数组的玩法,可以像数组一样初始化,注意这里没有用=链接{},剩下的默认值就是缺省值delete[] ptr;return 0;
}

语法:

// new  类型[数量N]{ 初始化内容 }
// delete [] 指针

对于自定义类型来说,同样受用。只不过是:调用了N次构造函数
例3.4:

#include<iostream>
using namespace std;class A
{
public:A(int a = 0):_a(a){cout << "A(int a) -- 构造" << endl;}A(const A& val):_a(val._a){cout << "A(const A& val) -- 深拷贝" << endl;}
private:
int _a;
};int main()
{A* ptr = new A[10]{111A(1)}; //隐式类型转换或者匿名对象都可以。delete[] ptr;return 0;
}
  • 在这里特别强调:一定要搭配使用开辟和释放

    • newdelete
    • new T[N]delete[ ]
    • mallocfree

一定不要混合使用!!!

4. new和delete底层

在这里不会深入地谈论new和delete的底层,需要的是大概了解其工作原理就可以了。

来看系统提供的两个全局函数operator newoperator delete:首先需要知道的是,newdelete的实现就是依靠的是operator newoperator delete。使用new就是调用函数operator new(运算符)
在这里插入图片描述
上面这张图片是operator newoperator delete的实现。我们从中读取关键信息:

  1. operator new实际上是通过malloc来申请空间的
  2. delete实际上调用的是free。事实也确实是这样,可是为什么需要绕一个弯来调用malloc呢?而不是直接new通过malloc来实现空间的开辟呢?
  • C++不想使用C语言那套通过返回值(错误码)来判断程序是否出现问题。而是希望抛出一个异常来告诉程序员,系统出BUG了,使用operator new的一部分原因也是因为希望开辟空间失败的问题让程序抛出异常,而不是通过返回值(设置错误码)来实现。

那么对于new来说,那么直接开空间就不适合直接套用malloc,而是需要采用operator new来进行进一步处理。所以大概调用的逻辑是:

在这里插入图片描述
实际上的new运行过程就是:malloc + 调用构造函数

所以在这里给出newdelete运行机制

  • new

    • 调用operator new申请空间
    • 在申请的空间上,进行构造函数初始化,完成对象的构建
  • delete

    • 在空间上执行析构函数,完成对象中的资源清理工作
    • 调用operator delete完成对空间的释放

new T[N]delete[ ] 皆是类似的道理

5. 对比new和malloc区别

经过上面的探讨,我们也看得出来newmalloc有些差距:

  1. new操作符malloc函数
  2. new内存开辟失败是抛异常malloc是返回nullptr设置错误码
  3. new不需要手动计算开辟内存大小,malloc需要传入总共字节数。
  4. new可以实现申请对象的初始化,而malloc不能实现初始化。
  5. new不需要强制类型转换指针,而malloc需要强制类型转换指针。
  6. 在进行自定义类型的申请时new开空间+调用构造函数delete调用析构函数+释放空间;而malloc不会对于自定义类型有任何额外的操作。
  • 实际上这里谈到了异常,还有内存泄露等一系列话题,我们在其它章节谈到。
  • 实际上new还有另外的玩法:定位newplacement new),这种特性,在这里不多做解释。

http://www.xdnf.cn/news/322057.html

相关文章:

  • 使用mindie部署qwen2_vl分析视频
  • 线程池详解,生命周期,线程池种类,预热
  • day18 python聚类分析对数据集模型性能影响
  • Content-Type使用场景及示例
  • 阿里云2核2g安装nexus
  • KL散度(Kullback-Leibler Divergence):概率分布差异的量化利器
  • 同步 / 异步、阻塞 / 非阻塞
  • 基于STM32、HAL库的SCD41-D-R2 气体传感器驱动程序设计
  • 数据中心机电建设
  • 【论文阅读】Attentive Collaborative Filtering:
  • 【MongoDB篇】MongoDB的分片操作!
  • FAST-LIO笔记
  • 【北京迅为】iTOP-4412精英版使用手册-第十章 QtE5.7系统编译
  • [OpenManus]部署笔记
  • Mkdocs文档引用相对地址的一些问题
  • 使用OpenCV的VideoCapture播放视频文件示例
  • 偏导数和梯度
  • shell-sed
  • MCP 规范新版本特性全景解析与落地实践
  • 图片文件转base64存储在数据库
  • redis端口漏洞未授权访问漏洞
  • Rust 中 Arc 的深度分析:从原理到性能优化实践
  • 2020年NCA CCF-C,改进灰狼算法RSMGWO+大规模函数优化,深度解析+性能实测
  • 鸿蒙开发——4.ArkTS快速入门指南
  • 我的世界云端服务器具体是指什么?
  • Laravel 12 实现验证码功能
  • 代码随想录算法训练营第三十四天
  • WordPress个人博客搭建(三):WordPress网站优化
  • RabbitMq学习(第一天)
  • 5.7 react 路由