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

【C++笔记】内存管理

前言

各位读者朋友们大家好,上期我们讲了类和对象下的内容,类和对象整体的内容我们就讲完了,接下来我们开启新的部分内存管理的讲解。

目录

  • 前言
  • 一. C/C++内存分布
  • 二. C语言中内存管理的方式
  • 三. C++内存管理方式
    • 3.1 new/delete操作内置类型
    • 3.2 new和delete操作自定义类型
  • 四. operator new和operator delete函数
    • 4.1 operate new和 operator delete函数
  • 五. new和delete的实现原理
    • 5.1 内置类型
    • 5.2 自定义类型
    • 5.3 使用不匹配
  • 六. 定位new表达式
  • 七. malloc/free和new/delete的区别
  • 结语

一. C/C++内存分布

C/C++的内存分为堆区、栈区、静态区和常量区,我们先来看下面的一段代码和相关问题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二. C语言中内存管理的方式

malloc/calloc/relloc的区别:

  • malloc是用来在堆区开辟指定字节的函数,返回的是void * 类型的指针。
    在这里插入图片描述
  • calloc函数是用来开辟指定字节空间的函数,并且把开辟的空间的值赋值为0,返回值也是void * 类型的指针。
    在这里插入图片描述
  • realloc函数是用来扩容的函数,第一个形参是要扩容的初始位置的指针,第二个参数是扩容后的空间大小,特别的如果第一个参数传的是空指针,那么realloc函数的功能等同于malloc函数的功能。如果原地扩容成功就返回传过去的指针,如果没有成功就会异地扩容,将原来的数据拷贝到新的位置,再将原位置的空间释放后返回新空间的地址。
    在这里插入图片描述
    在这里插入图片描述
    这里不需要释放p2,因为p3是在p2的基础上扩容的,如果原地扩容成功,p2和p3指向的空间是同一块,所以只需要释放p3即可;如果异地扩容,realloc函数就已经帮我们将p2释放了。

三. C++内存管理方式

C语言的内存管理方式在C++中可以继续使用,但是有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行内存管理。

  • new 运算符用于在堆(heap)上动态分配内存并初始化对象。它返回指向分配的内存块的指针。
  • delete 运算符用于释放先前使用 new 运算符分配的内存。

3.1 new/delete操作内置类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],一定要搭配起来使用

3.2 new和delete操作自定义类型

在这里插入图片描述
在这里插入图片描述

这样就很方便了,之前我们写链表要去申请节点,现在我们可以这样写:

struct ListNode
{
	int val;
	ListNode* next;

	ListNode(int x)
		:val(x)
		, next(nullptr)
	{}
};

int main()
{
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	ListNode* n4 = new ListNode(4);
	ListNode* n5 = new ListNode(5);
	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = n5;
	return 0;
}

在这里插入图片描述
这样就写好了一个链表。
在这里插入图片描述
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会调用。

四. operator new和operator delete函数

4.1 operate new和 operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。在c++中new是operator new 加构造函数,delete是operator delete加析构函数
在这里插入图片描述
在这里插入图片描述
通过上述两个全局函数的实现知道,operator new实际上也是通过malloc来申请空间。如果malloc申请成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该应对措施就继续申请,否则就抛异常。operator delete最终通过free释放空间。

五. new和delete的实现原理

5.1 内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请和释放的是连续的空间,而且new在申请失败时会抛异常,malloc失败会返回NULL。

5.2 自定义类型

new的原理
在这里插入图片描述
1. 调用operator new函数申请空间
2. 在申请的空间上执行构造函数,完成对对象的构造
delete的原理在这里插入图片描述
1. 在空间上执行析构函数,完成对象中的资源清理工作
2. 调用operator delete函数释放对象的空间
new T[N]的原理
在这里插入图片描述
1. 调用operator new[]函数,在operator new函数完成对N个对象空间的申请
2. 在申请的空间上执行N次构造函数
new T[N]可以理解为执行N次new
delete []的原理
在这里插入图片描述

1. 在释放的对象空间上执行N次析构函数,完成N个对象中的资源清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
实际上就是执行N次delete

5.3 使用不匹配

  • 内置类型new和free混用
    在这里插入图片描述
    这里不会报错,因为内置类型调用delete时不需要调用析构函数,只调用了_free_dbg也就是free,因此也不会有内存的泄露。但是不建议这样使用。
  • 自定义类型new和delete混用
    在这里插入图片描述
    这里调用free相比调用delete来说少调用了析构函数,如果A的析构函数有对内存的释放就会存在内存泄露的风险。
  • 内置类型new和delete[] 混用
    在这里插入图片描述
    这里不会有内存泄露的风险,因为new 底层调用的malloc,delete底层调用的free。
  • 自定义类型new[]和delete混用
    在这里插入图片描述
    B类中不存在内存的申请和释放,所以不会内存泄漏。
    但是下面这种情况程序会崩:
    在这里插入图片描述
    这是为什么?
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "(int a = 0)" << endl;
	}
	A(int a ,int b)
		: _a(a)
		,_b(b)
	{
		cout << "(int a = 0,int b = 0)" << endl;
	}
	~A()
	{
		cout << "~A():" << endl;
	}
private:
	int _a;
	int _b;
};

class B
{
private:
	int _b1 = 520;
	int _b2 = 1314;
};

这里A和B的大小都是8个字节
我们还是从汇编来看:
对于B,编译器给了80个空间
在这里插入图片描述
而同样大小,相同个数的A却开了84字节的空间
在这里插入图片描述
在这里插入图片描述
编译器在给A开空间时多开了4个字节用来存储A的元素个数,而返回的地址却没用从多开的4个字节开始返回,而是往后偏移了4个字节,所以会崩。严格来说两者都应该多开4个字节存元素个数,B没有开是因为编译器优化了,因为编译器看到B没有写析构函数而且没有内存的申请和释放,元素个数是给delete[]用的,当使用delete[]时,指针向前偏移四个字节取到元素个数(释放还是在原位置释放),让编译器知道调几次析构函数。B编译器优化到底,不调用析构函数,所以就不存个数。
如果给B写了析构函数,编译器也会开84个字节的空间:
在这里插入图片描述

六. 定位new表达式

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new(place_address)type 或者new(place_address)type(initializer_list)
place_address必须是一个指针,initializer_list是类型的初始化列表
在这里插入图片描述
上图调用operator new只开了空间并没有初始化,
在这里插入图片描述
在这里插入图片描述
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式对其进行显示调用构造函数进行初始化。

七. malloc/free和new/delete的区别

malloc/free和new/delete的共同特点是:都是在堆上申请空间,都需要用户去手动释放。
不同在:

  • 1. malloc和free是函数,new和delete是操作符
  • 2. malloc申请的空间不会初始化,new可以初始化。
  • 3.malloc申请对象时,需要自己计算空间大小并传递,new只需在其后跟上空间类型即可,如果是多个对象,[]中指定个数即可。
  • 4.malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型。
  • 5.malloc申请空间失败时,返回的是NULL,所以使用的时候必须判空,new不需要,但是new需要捕获异常。
  • 6.申请自定义类型对象时,malloc/free只会开辟和释放空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放.

结语

以上我们就讲完了C++的内存管理,这里还有一些东西没有讲解,需要后续在讲解。感谢大家的阅读,欢迎大家批评指正!


http://www.kler.cn/news/366907.html

相关文章:

  • nfs服务部署案例
  • 「二叉树进阶题解:构建、遍历与结构转化全解析」
  • Leetcode刷题笔记12
  • 高效文本编辑与导航:Vim中的三种基本模式及粘滞位的深度解析
  • 【算法——1维动态规划具体例题】
  • 【Java网络编程】从套接字(Socket)概念到UDP与TCP套接字编程
  • 实现简道云与企业微信的自动化数据集成
  • [C#][winform]基于yolov8的道路交通事故检测系统C#源码+onnx模型+评估指标曲线+精美GUI界面
  • Java-图书管理系统
  • LeetCode105. 从前序与中序遍历序列构造二叉树(2024秋季每日一题 49)
  • 地磁传感器(学习笔记上)
  • 微信小程序文字转语音播报案例
  • 基于Java SpringBoot和Vue社区医院诊所医疗挂号管理系统设计
  • 【超大数据】数字的拆分——int128数据类型的使用方法
  • Spring Cloud微服务:构建现代应用的新基石
  • 【笔记】软件测试09——接口测试
  • 图文深入理解Oracle Total Recall
  • Ubuntu虚拟机无法启动,无U盘拯救
  • GoogleChrome和Edge浏览器闪屏问题
  • 图片懒加载
  • MATLAB工具箱使用案例详解
  • Vuejs设计与实现 — 编译层面的优化
  • Walrus + Sui:如何充分发挥Web3的潜力
  • vue中$nextTick的作用是什么,什么时候使用
  • docker-minio启动参数
  • vue3嵌套路由二级跳转三级路由时,如何阻止走二级的生命周期