C++语法|深入理解 new 、delete

news2024/11/17 15:32:41

在开发过程中,非常重要的语法就有我们new和delete,周所周知在C++中最为强大的能力就是对内存的控制,所以我们再怎么强调new和delete都不为过

文章目录

  • 1.new和delete基本语法
    • new和malloc的区别是什么?
      • (1)开辟单个元素的内存差别
      • (2)开辟数组内存语法差别
      • (3)开辟类类型的语法差别
      • (4) 为结构体类型分配内存
    • new有多少种?
  • 2.new和delete的函数重载
    • 重载operator new 和 operator delete
    • 重载new[]和delete[]
    • 重载 new 和delete的典型应用
  • 3.面试问题:new\new[]和delete\delete[]能混用吗(C++为什么区分单个元素和数组的内存分配和释放)?

1.new和delete基本语法

在本节中,我们会通过对比malloc和free来对比讲解new和delete的基本语法。

new和malloc的区别是什么?

我觉得想要完整描述他们两个的区别就是要从以下四个层次来进行描述:
开辟单个内存、开辟数组内存、开辟结构体内存和开辟类类型的内存。

还有个一句话总结,
malloc是一个库函数,而new是运算符,所以调用的方法肯定是不一样的。
malloc按字节开辟内存,只分配原始内存,不会调用构造函数,所以在不涉及到类类型的时候,其实用哪个语法上都差不多。
new不仅会分配内存内存,还会调用析构函数,并且对应的delete也会去调用析构山函数,但是free一个类类型首先需要显式调用析构函数,然后free这块内存。

所以说申请一段内存就调用malloc(更轻便),如果为类对象申请内存,请使用new!

(1)开辟单个元素的内存差别

malloc是按照字节来开辟内存的,返回值是void。需要对返回的类型进行一个强转*。并且对于开辟内存失败的语法就是通过返回值来进行检测的。
并且,我们需要对这块内存尽兴初始化,因为malloc可不帮我们进行初始化,他只负责给我们开辟内存

int *p = (int*)malloc(sizeof(int)); //molloc按照
if (p == nullptr) { //返回值是空说明内存开辟失败
    return -1; 
}
*p = 20;

然后它直观开辟内存,他不管初始化,我们需要单独用*p的代码来进行一个初始化的操作,释放这个p的内存需要一个free的库函数。它的语法也是把这块内存的起始地址传进来。

free(p)

也就是说new不仅可以做内存开辟,还可以做内存初始化操作,并且new可以直接指定开辟内存的类型,所以也不需要强转; 可以直接用小括号指定初始值

而new开辟内存失败是通过抛出bad_alloc类型的异常来判断。一般是使用try 和 catch

try {
	int *p1 = new int(20);
} catch (const bad_alloc &e) {

}

释放内存直接delete p1即可。

delete p1;

(2)开辟数组内存语法差别

分别使用malloc和new开辟数组内存如下代码:

int *q = (int*)malloc(sizeof(int) * 20);
if (q == nullptr) {
    return -1;
} 
free(q);

//int *q1 = new int[20];这样写的话堆上只负责开辟数组而不进行初始化
int *q1 = new int[20](); //20个int 。并且初始化所有元素为0
delete[]q1;

在开辟数组内存的语法中,malloc仍然遵循——指定要开辟的内存大小即可;
但是new却连语法都一样了,需要用到一个[]中括号来指明需要开辟的数组个数。并且new只支持数组元素初始化为0。

为什么C++里面要把释放单个元素内存和数组内存分开呢?
原因不体现在简单类型上,而是体现在面向对象的类类型上,我们在new的函数重载中会讲到讲到。

(3)开辟类类型的语法差别

我们一定要抓住关键点:
malloc 是 C 库函数,用于在堆上分配指定字节数的内存,但它只分配内存,不调用类的构造函数。因此,如果你使用 malloc 来分配一个类对象的内存,你需要手动调用构造函数(通常通过 placement new)并且手动管理初始化和销毁。

如果使用new来申请一个类类型,那就简单多了:

(4) 为结构体类型分配内存

其实如果在C++中,如果结构体类型没有写构造函数,编译器会自动生成一个默认构造函数。默认构造函数会将所有内置类型的成员初始化为未定义值(对于基本数据类型)。
此时malloc和new的使用没有什么本质上的区别。

new有多少种?

  • 抛出异常版本的new
int *p1 = new int(20);
  • 不抛出异常版本的new
int *p2 = new (nothrow) int; 
  • 常量new:在开辟常量new的时候,我们一定要用常量指针来指向这块内存,不然就会抛出错误。
//抛出错误,在堆上抛出了一个常量
//int *p3 = new const int(40);
//既然是使用常量我们就不能把它当一个普通内存,我们必须使用常量指针去指向这块内存
const int *p3 = new const int(40); 
  • 定位new:常常用在类类型的构造和销毁
int data = 0;
int *p4 = new (&data) int (50);

首先我们知道这里的data是在栈内存上的。当我们使用定位new,此时new就不会再在堆上为我们分配内存了,而是在已经存在的内存地址(即&data)上构造对象。
也就是说new (&data) int(50) data 的地址上构造一个 int,并初始化为 50。并且data仍然在栈上。

八股背诵总结:
malloc和new的区别
1.malloc按字节开辟内存,new开辟内存时要指定类型,所以malloc开辟内存返回的都是void* ,new的返回值自动转换成我们指定的指针类型operator new -> int*
2.malloc只负责开辟空间,new不仅仅有malloc的功能,还可以进行数据的初始化
3.malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
free和delete区别:
delete:调用析构函数,free不会调用

2.new和delete的函数重载

从汇编代码上可以看出,new的调用本质上是对operator new函数的调用。delete的调用本质上是对operator delete函数的调用。所以我们其实可以自己进行该函数的重载。

重载operator new 和 operator delete

//先调用oeprator new开辟内存空间、然后调用对象的构造函数(初始化)
void* operator new(size_t size) {
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc;
	cout << "operator new addr: " << p << endl;
	return p;
}

// delete p;调用p指向对象的析构函数、再调用operator delete释放内存空间
void operator delete (void *ptr) {
	cout << "operator delete addr: " << ptr << endl;
	free(ptr);
}

int main () {
	int *p = new int;
	delete p;
	return 0;
}

现在编译器链接的就是我们自己实现的operator new 和 operator delete,而不会链接C++库的这两个函数
如果想判断是否开辟内存失败:

try {
	int *p = new int;
	delete p;
} catch (const bad_alloc &err) {
	cerr << err.what() << endl;
}

重载new[]和delete[]

void* operator new[] (size_t size) {
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc;
	cout << "operator new[] addr: " << p << endl;
	return p;
}

void operator delete[] (void *ptr) {
	cout << "operator delete[] addr: " << ptr << endl;
	free(ptr);
}

我们new一个数组进行一下测试:

try {
	int *p = new int;
	delete p;

	int *q = new int[10];
	delete[] q;
} catch (const bad_alloc &err) {
	cerr << err.what() << endl;
}

可以看出结果非常完美

什么时候要重载operator new 和 operator delete呢?
我们的应用或者整个的系统整个内存管理有我们自己的实现方案,比如说实现一个内存池,否则我们就规规矩矩使用库里面的这些函数即可。
除非我保证当前应用我自己解决内存管理的方案比new和delete都好,否则就用库里面的new和delete即可。
(内存池的使用场景:对于实时性要求比较强的,对于小块内存频繁进行分配和释放的应用最好是实现内存池)

总而言之,如果我们重写operator new和operator delete我们就可以接管整个程序的内存管理。

重载 new 和delete的典型应用

如何设计一个程序可以让其检查C++程序内存泄漏的问题
有内存泄漏那就说明new操作肯定没有对应的delete,我们可以在全局去重写 operator new 和 operator delete函数,在new操作里面我们用映射表记录一下我们都有哪些内存被开辟过了,然后释放的时候把相应的内存资源删除掉。如果我们映射表中表示还有一些内存没有被释放,那就肯定存在内存泄漏的问题。

#include <iostream>
#include <unordered_map>
#include <cstdlib>

std::unordered_map<void*, size_t> allocations;

void* operator new(size_t size) {
    void* ptr = std::malloc(size);
    if (ptr) {
        allocations[ptr] = size;
    }
    return ptr;
}

void operator delete(void* ptr) noexcept {
    auto it = allocations.find(ptr);
    if (it != allocations.end()) {
        allocations.erase(it);
    }
    std::free(ptr);
}

int main() {
    int* p = new int[10];

    // 故意不释放内存以测试泄漏检测
    // delete[] p;

    // 检查内存泄漏
    if (!allocations.empty()) {
        std::cout << "Memory leaks detected:" << std::endl;
        for (const auto& alloc : allocations) {
            std::cout << "Leaked " << alloc.second << " bytes at " << alloc.first << std::endl;
        }
    } else {
        std::cout << "No memory leaks detected." << std::endl;
    }

    return 0;
}

3.面试问题:new\new[]和delete\delete[]能混用吗(C++为什么区分单个元素和数组的内存分配和释放)?

如果是开辟单个的元素和数组的话,new和delete混用是没有任何问题的,因为此时他们只剩malloc 和 free 的功能

但是如果设计到类类型,那就不行了。我们还是跟之前一样实现operator new 和 operator delete
比如说我们设计以下类:

void* operator new(size_t size) {
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc;
	cout << "operator new addr: " << p << endl;
	return p;
};
void operator delete (void *ptr) {
	cout << "operator delete addr: " << ptr << endl;
	free(ptr);
};
class Test {
public:
	Test(int data = 10) { cout << "Test()" << endl}
	~Test() { cout << "~Test()" << endl;}
private:
	int ma_;
}

首先我们来测试operator new和operator delete[]的混用。

int main () {
	Test *p1 = new Test();
	//delete[] p1; 混用会报错,无法正常析构
	delete p1;
}

如果使用delete[] p1,会不停去析构Test而无法正常去释放内存。这是为什么呢?因为我们在开辟内存类对象和数组对象的时候,系统会为我们在返回给我们的指针的前面开辟一块内存(可能4字节、可能16字节)来存储我们开辟对象的个数,但是我们使用的是new而不是new[],所以前面根本就没有存储对象个数的内存,然而我们却调用delete[]来释放内存,系统就会去找存储对象内存个数的那块内存,所以酷酷报错。


现在我们来测试operator new[]和operator delete

int main () {
    Test *p2 = new Test[5];
    //delete p2;
    delete[] p2;
}

我们构造了5个Test类并且为它分配内存,但是如果使用delete p2,只能析构最后构造出来的Test类。(有的编译器会直接报错,进程直接崩溃)
结果如下:

operator new addr: 0x12f606de0
Test()
Test()
Test()
Test()
Test()
~Test()
operator delete addr: 0x12f606df0

我们从0x12f606de0地址但是却从0x12f606df0释放,有16个字节的内存泄漏!

总结:

  • 对于不同的编译器内置类型,new和delete[]混用 或者是 new[]和delete混用是完全没问题的,因为内置类型没有构造析构可言,只涉及内存开辟释放,而内存开辟释放的底层就是malloc和free。
  • 如果我们自己定义了类类型,就必须搭配使用而不能混用。因为其实我们调用new[]时,系统不仅仅会为我们开辟对应个数的内存空间,而且还会在开辟地址的首地址记录存储对象的个数(该区域一般占4个字节,我的电脑上占16个字节)。
    如果我们用new[]开辟,但是用delete来释放内存,系统会以为我们只开辟了一个类对象的内存,所以只会释放一个类对象的内存,就让我们上文中的输出operator new addr: 0x12f606de0 operator delete addr: 0x12f606df0
  • 自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候,回多开辟4个字节记录对象的个数。所以我们不能随意混用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1675908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python专题:十三、日期和时间(2)

datetime 模块 today()函数 date类型 year month day

摊还分析

一、摊还分析 概念&#xff1a;是求数据结构中一个操作序列执行所有操作的平均时间&#xff0c;与平均复杂度不同&#xff0c;它不涉及输入概率&#xff0c;能够保证在最坏情况下操作的平均性能。 适用场景&#xff1a;用含 n 个操作的序列&#xff08;o1&#xff0c;o2&#x…

互联网轻量级框架整合之HibernateMyBatis

持久层框架 Hibernate 假设有个数据表&#xff0c;它有3个字段分别是id、rolename、note, 首先用IDEA构建一个maven项目Archetype选择org.apache.maven.archetypes:maven-archetype-quickstart即可&#xff0c;配置如下pom <project xmlns"http://maven.apache.org/…

ERROR: tensorboard 1.14.0 has requirement setuptools>=41.0.0(问题解决)

问题描述&#xff1a; ERROR: tensorboard 1.14.0 has requirement setuptools>41.0.0, but youll have setuptools 39.2.0 which is incompatible. 问题原因&#xff1a; setuptools 版本太低 解决方法&#xff1a;升级setuptools版本 pip install --upgrade setuptools…

Notepad8.1.9汉化中文版

下载地址&#xff1a;Notepad8.1.9.zip 一款优秀免费的源代码编辑器&#xff0c;支持语法高亮的开源纯文本编辑器&#xff0c;并且可以替代系统自带的记事本的功能。

40V/300mA快速瞬态低压差线性稳压器替代ADP7142

概述(替代ADP7142/LT1962/LT1521/LT3060) PCD4900 是一款 CMOS、低压差&#xff08;LDO&#xff09;线性稳压器&#xff0c;采用 1.9V 至 40V 电源供电&#xff0c;最大输出电流为 300 mA。这款高输入电压 LDO 适用于调节 36V 至 1.2V 供电的高性能模拟和混合信号电路。该器件…

iOS copy的正确姿势

参考文章 知识准备&#xff08;理解堆栈&#xff09; 堆区&#xff1a; 程序员管理 若程序员不释放&#xff0c;由os释放不同于数据结构中的堆&#xff0c;堆区的结构类似于数据结构中的链表栈区&#xff1a; 由编译器来管理 存放函数参数值&#xff0c;局部变量的值等结构类似…

C语言(指针)7

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

nestjs封装一个响应体

封装一个DTO // response.dto.tsimport {CallHandler,ExecutionContext,Injectable,NestInterceptor, } from "nestjs/common"; import { FastifyReply } from "fastify"; import { Observable } from "rxjs"; import { map } from "rxjs/…

苹果macOS无法给App麦克风授权解决办法

好久没有在电脑上录制课程了&#xff0c;有些东西还是录下来记忆深刻&#xff0c;却意外发现MAC系统升级后无法授权给第三方的App使用摄像头和麦克风&#xff0c;而录屏软件是需要开启麦克风和摄像头才能录制屏幕上的操作和声音&#xff0c;官方提示在第三方APP若有使用摄像头和…

echarts切换tab,图表设置宽度100%结果为100px

当外层div盒子设置宽度为100%时&#xff0c;可echarts渲染出来宽度只有100px&#xff0c;这种情况大多数echarts所在的div设置了display:none&#xff0c;获取不到外部盒子的宽度。 这里可以通过echarts源码获取宽度的行为来解释 究其原因就是出现在了图表设置了display:none…

【从零开始实现stm32无刷电机foc】【理论】【1/6 电机旋转本质】

目录 电机旋转需要什么样的力&#xff1f;怎么产生力矢量&#xff1f;怎么产生任意的线圈磁矢量&#xff1f; 电机旋转需要什么样的力&#xff1f; 电机切向存在受力&#xff0c;电机就会旋转。 进一步查看电机结构&#xff0c;分为转子和定子&#xff0c;大部分情况下&#…

超级简单的地图操作工具开发可疑应急,地图画点,画线,画区域,获取地图经纬度等

使用echars的地图画点,画线,画区域,获取地图经纬度等 解压密码:10086007 地图也是用临时的bmap.js和china.js纯离线二选一 一共就这么多文件 画点,画线,画区域 点击地图获取经纬度-打印到控制台,这样就能渲染航迹,多变形,结合其他算法算圆等等操作 下载资源:https://download…

ROS2 conda 环境下报 No module named ‘catkin_pkg‘

这里使用的环境是&#xff1a;Ubuntu 22.04, ROS2, Conda python 虚拟环境是3.10 ~/.bashrc 里注释掉 conda&#xff0c;并添加 export PYTHONPATH$PYTHONPATH:/usr/lib/python3/dist-packages sudo gedit ~/.bashrc source ~/.bashrc

使用Remix部署智能合约到币安链(Remix的操作介绍 币安链合约的部署) 点赞收藏哦

大家好&#xff0c;我是程序员大猩猩呀。 据我所知&#xff0c;很多人进入币圈之后&#xff0c;想要通过炒币一夜暴富&#xff01;另一部分人呢他们希望自己能创建一个项目&#xff0c;然后发行自己的数字货币然后暴富。 不管是什么方式吧&#xff0c;只要不违法&#xff0c;…

Spring框架中常见注解

Spring&#xff1a; SpringMVC&#xff1a; RequestMapping用在类上表示所有该类下方法的父路径 RequestParam 做映射&#xff0c;前端请求的参数映射到控制器Controller的处理方法上的参数上。 【当参数需要设置默认值&#xff08;前端没有发送这个参数&#xff09;、参数名…

面对.halo勒索病毒,如何有效防范与应对?

导言&#xff1a; 随着网络技术的不断发展&#xff0c;网络安全问题也日益凸显。其中&#xff0c;勒索病毒作为一种极具破坏性的网络攻击手段&#xff0c;近年来在全球范围内频发。其中&#xff0c;.halo勒索病毒作为勒索病毒家族中的一员&#xff0c;其危害性和传播性不容忽视…

供水设备数据采集

随着城市化进程的加快&#xff0c;供水系统作为城市基础设施的重要组成部分&#xff0c;其运行效率和稳定性直接关系到市民的日常生活。在这个信息化、智能化的时代&#xff0c;如何利用先进技术提升供水系统的管理水平&#xff0c;成为了摆在我们面前的重要课题。HiWoo Cloud平…

链表----带环链表快慢指针进阶版

1.带环链表及其拓展 &#xff08;1&#xff09;这个题目组要就是进行判断这个链表是否带环&#xff0c;使用的是布尔类型作为返回值&#xff1b; &#xff08;2&#xff09;我们这里的思路是使用的快慢指针&#xff0c;快指针一次走2步&#xff0c;慢指针一次走1步&#xff0c…

泰山众筹:创新电商模式引领共赢新潮流

一、泰山众筹模式创新解读 泰山众筹&#xff0c;这一电商领域的创新模式&#xff0c;通过巧妙地将产品销售与积分众筹相结合&#xff0c;为用户和平台带来了双赢的局面。在泰山众筹模式下&#xff0c;用户购买产品的同时能够积累积分&#xff0c;这些积分可以作为参与众筹的筹…