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

在开发过程中,非常重要的语法就有我们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.mzph.cn/bicheng/12434.shtml

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

相关文章

火遍全网的“当当狸智能激光雕刻机L1” 让创意梦想分分钟实现

当当狸首款“桌面级”智能激光雕刻机来袭&#xff0c;千万别错过。 龙年伊始&#xff0c;当当狸就迎来了新品首发——智能激光雕刻机L1。 话不多说&#xff0c;赶快来看~~ 当当狸这款智能激光雕刻机造型美观&#xff0c;设计时尚&#xff0c;堪称激光雕刻机界的颜值天花板~~ …

类型的转换

首先我们要了解java中的数据类型转换是指将一种数据类型转换成另一种数据类型的过程。 什么时候会用到&#xff1f;我觉得两种情况会用到 等号左右两边类型不一致&#xff08;一般发生在赋值时&#xff09;不同类型的数据参与运算&#xff08;一般发生在计算时&#xff09; 转…

C函数总结

perror&#xff08;s&#xff09; 头文件&#xff1a;stdio.h功能&#xff1a;将函数发生错误的原因打印到标准设备&#xff08;stderr&#xff09;&#xff0c;参数s所指的字符串会先打印出&#xff0c;后面加上错误原因。 realloc 头文件&#xff1a;stdlib.h功能&#xf…

空降领导生存指南

1、有真本事&#xff0c;亲自解决棘手的问题。 2、善待部下 3、创造价值 4、找大老板帮忙站台&#xff0c;吃饭&#xff0c;开会 5、开始找两个行业深度知识开会&#xff0c;培训&#xff0c;比如EMC… 6、软着陆&#xff0c;观察业务了解人。刚到新单位不要马上定标准&#xf…

matlab使用1-基础

matlab使用1-基础 文章目录 matlab使用1-基础1. 界面介绍2. matlab变量3. matlab数据类型4. matlab矩阵操作5. matlab程序结构5.1 顺序结构5.2 循环结构5.3 分支结构 1. 界面介绍 命令行窗口输入&#xff1a;clc 可清除命令行窗口command window的内容 clc命令行窗口输入&…

独家揭秘:亲历清华大学答辩现场,惊喜万分 名校答辩不简单

会议之眼 快讯 五月&#xff0c;对于学术界来说&#xff0c;迎来了答辩的高潮&#xff01;是收获的季节&#xff01;今天&#xff0c;趁着阳光明媚&#xff0c;小编怀揣着对学术探索的无限热情和好奇心&#xff0c;决定亲自踏入中国顶尖学术殿堂——清华大学深圳国际研究生院&…

软件产品质量模型及其子特性

一、功能性 子特性&#xff1a; 功能的完备性 功能正确性 功能适合性 功能性的依从性 二、性能效率 子特性&#xff1a; 时间特性 资源利用性 容量 性能效率的依从性 三、兼容性 子特性&#xff1a; 共存性 互操作性 兼容性的依从性 四、易用性 子特性&#xff1a; 可辨识性…

洁太司检测试剂盒:肝癌早诊新利器,共筑健康未来

随着科技进步及医疗技术的不断创新&#xff0c;人类对疾病的早期诊断和治疗提出了更高的要求。 先思达生物近期推出的“洁太司-寡糖链检测试剂盒”&#xff0c;在原发性肝细胞癌的诊断领域实现了重大突破&#xff0c;获得了国家药品监督管理局&#xff08;NMPA&#xff09;的三…

[牛客网]——C语言刷题day2

答案&#xff1a;B 解析&#xff1a; char *p[10] 是指针数组,数组里存放了10个指针,在64位系统下指针占8个字节,所以sizeof(p) 10 * 8 80. char (*p1)[10]是数组指针,p1是一个指向存放10个char类型的数组的指针,所以sizeof(p1) 8. 答案&#xff1a;B 解析&#xff1a…

1830. 【提高组NOIP2007】统计数字(count.pas/c/cpp)

题目描述 某次科研调查时得到了n个自然数&#xff0c;每个数均不超过1500000000&#xff08;1.5*10^9&#xff09;。已知不相同的数不超过10000个&#xff0c;现在需要统计这些自然数各自出现的次数&#xff0c;并按照自然数从小到大的顺序输出统计结果。 输入 从文件 cou…

【大道至简】官方兼容到android13+的获取系统屏幕高度, statusbar,navBar

android在屏幕高度和app高度&#xff0c;statusbar, navigationbar的高度处理上&#xff0c;迭代了好多版本。 android11&#xff0c; android12都有新的api和过时的api标记。 涉及的api类似如下&#xff1a; windowManager&#xff0c;defaultDisplay, Context.display, Deco…

分布式系统的一致性与共识算法(二)

Consitency 背景 如买最后一张车票&#xff0c;两个售票处分别通过某种方式确认过这张票的存在。这时&#xff0c;两家售票处几乎同时分别来了一个乘客要买这张票&#xff0c;从各自"观察"看来&#xff0c;自己一方的乘客都是先到的&#xff0c;这种情况下&#xf…

思科模拟器学习1--Vlan Trunk

实验说明&#xff1a;将三台电脑的vlan 加到一台交换机里面&#xff0c;为了验证什么是虚拟局域网&#xff0c;把一个设备隔成三个空间&#xff0c;三个电脑互相不能通讯&#xff1b;目的是&#xff1a;vlan 1的通讯不可以向vlan 2传送&#xff0c;就是消息传送互不干扰的&…

解决Android Studio Gradle下载慢的问题

安卓 gradle-7.5-bin.zip 下载慢 https://mirrors.cloud.tencent.com/gradle/7.x.x 找到对应匹配版本 把下载的文件直接复制到 C:\Users\Administrator.gradle\wrapper\dists\gradle-x.x\ 中对应版本目录下&#xff0c;例如需要下载 gradle-2.14.1-all.zip&#xff0c;则下载好…

【Pytorch】torch.nn.conv2d

这个函数和我们之前提到的【Pytorch】6.torch.nn.functional.conv2d的使用的作用相似&#xff0c;都是完成CV领域的卷积操作&#xff0c;这里就不在过多赘述 torch.nn.conv2d的使用 打开pytorch的官方文档&#xff0c;我们可以看到 torch.nn.conv2d包含了若干参数 in_channe…

考研数据结构:栈与队列

栈与队列 栈Stack 栈的结构体定义及基本操作。 #define MaxSize 50 typedef struct {int data[MaxSize];//栈中存放数据类型为整型int top;//栈顶指针 }Stack; void InitStack(Stack& S) {//初始化栈S.top -1; } int IsEmpty(Stack S) {//判断栈是否为空if (S.top -1)…

一文读懂 Pencil 积分,打开 Pencils Protocol 生态权益大门

近日&#xff0c;Scroll 生态项目 Penpad 近期将品牌全新升级为 Pencils Protocol &#xff0c;在升级后&#xff0c;其从一个 Scroll 生态的原生 LaunchPad 平台进一步拓展为集 Staking、Vault 以及 Shop 等功能于一体的全新生态。全新的品牌升级不仅让 Pencils Protocol 生态…

ubuntu18.04系统安装pangolin

1. 安装pangolin依赖项 ctrlaltt 打开终端&#xff0c;依次输入下面的命令 sudo apt update sudo apt upgrade sudo apt install libglew-dev cmake libboost-dev libboost-thread-dev libboost-filesystem-dev libeigen3-dev -y 2.在终端中输入下面的命令&#xff0c;克隆…

2024 年第一季度全球互联网中断事件

2024 年第一季度伊始&#xff0c;互联网发生了多起中断事件。陆地和海底电缆的损坏在多个地方造成了问题&#xff0c;而与持续中地缘政治冲突相关的军事行动影响了其他地区的连接。 几个非洲国家以及巴基斯坦的政府下令关闭互联网&#xff0c;主要针对移动网络连接。 被称为Ano…

Pyqt中QThread传递自己定义的参数、类、函数

Pyqt中QThread传递自己定义的参数、类、函数 1 pyqt中Qthread传递自己定义的参数2 pyqt中Qthread传递自己定义的类3 pyqt中Qthread传递自己定义的函数4 pyqt中Qthread内部定义自己的函数5 pyqt中Qthread传递参数到内部定义自己的函数 1 pyqt中Qthread传递自己定义的参数 在PyQ…