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

c++图解内存管理

c&c++内存管理

文章目录

    • c&c++内存管理
      • 1.了解一些基本的内存段(图演示)
        • 验证栈是向下生长的
        • 验证堆一般是向上生长的(不一定)
        • 习题检测,巩固内存管理知识点
        • 答案
      • 2.c++申请动态内存的新玩儿法new,delete
        • 回顾c语言动态内存管理的方式
        • 开辟内置类型的空间
        • 开辟自定义类型的空间(请用vs2013以上版本测试代码)
        • 总结一下
      • 3. 32位平台下可以开辟多大的内存
        • 如何开辟4 G的内存
      • 4.了解`operator new`和`operator delete`
      • 5.`operator new`和`operator delete`的类函数重载--了解即可
      • 6.定位new--placement-new
      • 7.`malloc`/`free`和`new`/`delete`的区别--常考面试题
      • 8.再次理解内存泄漏

1.了解一些基本的内存段(图演示)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cvc8Pg5v-1662721223978)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909150153993.png)]


验证栈是向下生长的

#include<iostream>
using namespace std;

int main()
{
	int a = 3;
	int b = 4;
	int c = 5;
	int d = 6;
	cout <<"a:"<< &a << endl;
	cout << "b:"<<&b << endl;
	cout << "c:"<<&c << endl;
	cout << "d:"<<&d << endl;
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0uAzdZ7-1662721223980)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909150331712.png)]


验证堆一般是向上生长的(不一定)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbXWkaAG-1662721223982)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909150458987.png)]


#include<iostream>
using namespace std;

int main()
{
	int num = 10;

	while (num--)
	{
		int *p1 = (int*)malloc(sizeof(int));
		int *p2 = (int*)malloc(sizeof(int));

		cout <<"p1"<< p1 << endl;
		cout <<"p2"<<p2 << endl;
		cout << endl;

		free(p1);
	}
	return 0;
}

一般情况下,p1的地址是比p2的地址高的(因为堆一般是向上生长的),但是有时候是不一定的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YA6XBQTd-1662721223984)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909150957425.png)]


习题检测,巩固内存管理知识点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QSOGc1gW-1662721223987)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909151143358.png)]


答案

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JX4YNH5N-1662721223989)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909151326885.png)]


温馨提示:题目中的指针是局部指针变量,是在栈上的,但是它指向的内容(解引用)可能是堆区或者常量区的。,可以画画图理解理解


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ORAJtYl-1662721223990)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909152715315.png)]


2.c++申请动态内存的新玩儿法new,delete

回顾c语言动态内存管理的方式

malloccallocrealloc

  1. malloc堆上动态开空间

  2. calloc堆上动态开空间+初始化成0等价于malloc+memset

  3. realloc指针已有的空间扩容

    原题增容–后面又足够的空间

    异地增容–后面没有足够的空间

开辟内置类型的空间

//C++开辟动态内存的新玩法
//语法演示:
#include<iostream>
using namespace std;

int main()
{
	//申请一个int的动态空间
	int* p1 = (int*)malloc(sizeof(int));
	*p1 = 1;
	int* p2 = new int(2);//这里是初始化

	free(p1);
	delete p2;

	//申请一个10各int的动态数组
	int *p3 = (int*)malloc(sizeof(int)* 10);
	for (int i = 0; i < 10; i++)
	{
		p3[i] = i + 1;
	}
	int *p4 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//初始化

	free(p3);
	delete[]p4;
	return 0;
}

跟c语言的版本相比,c++的版本,初始化时显得比较方便,而且语法比较简洁。当然了,更大的优越性还在后面。


开辟自定义类型的空间(请用vs2013以上版本测试代码)

#include<iostream>
using namespace std;

struct ListNode
{

	int _val;
	ListNode* _next;
	ListNode* _prev;

	//构造函数
	ListNode(int val = 0)
	:_val(val)
	, _next(nullptr)
	, _prev(nullptr)
	{
        cout<<"ListNode(int val = 0)"<<endl;
    }
	
	~ListNode()
	{
		cout << "ListNode()" << endl;
	}
};

int main()
{
	//申请一个结点的空间
    ListNode* pa = (ListNode*)malloc(sizeof(ListNode)*4);
	ListNode* pb = new ListNode(1);//不用去用sizeof去计算空间大小,很方便   
	free(pa);
	delete pb;

	//申请4个结点的空间--当然了一般不会这么玩儿,我们只是看看效果
	ListNode* pc = (ListNode*)malloc(sizeof(ListNode)* 4);
	ListNode* pd = new ListNode[4]{1, 2, 3, 4};

	free(pc);
	delete[]pd;

	return 0;
}

​ 学过c语言版本的数据结构的小伙伴都知道,我们push_back一个结点时,需要先实现一个buynewnode的函数(创建一个结点,并且对其进行初始化)。而new这个操作符,在创建结点的同时,已经帮我们实现了结点的初始化。调用了构造函数,而且delete还调用了析构函数。

总结一下

  1. c++中,如果是申请内置类型对象或者数组,mallocnew没有太大区别
  2. 如果时自定义类型,区别很大,new和delete时开空间+初始化析构清理+释放空间mallocfree仅仅时开空间+释放空间
  3. 建议在c++中,无论时内置类型还是自定义类型的申请释放,尽量使用new和delete

3. 32位平台下可以开辟多大的内存

我:cpu过来遭罪
cpu:你不要过来啊
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iDGYP8oA-1662721223992)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909155816881.png)]

上述程序,我们开了1.8 G左右的内存,加上需要堆上的小内存,最后的综合有2 G的空间


如何开辟4 G的内存

将项目属性修改一下,改成64位平台即可,64位平台有2^34 G的空间(虚拟内存)在这我们不做细说,因为我也不太了解Linux。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJbYU4Lc-1662721223993)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909160504612.png)]


4.了解operator newoperator delete

new其实是operator new + 构造函数

delete其实是operator delete+构造函数

newdelete是用户进行动态内存申请和释放的操作符operator newoperator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

大家可以将operator new和operator delete理解成和malloc 和free一样用法的函数。

唯一不同的地方是,operator new和malloc开辟空间失败的处理方式不一样,malloc是返回NULL空指针,而operator是抛异常,下面代码让大家看看效果,不必深究,以后会有介绍。

struct ListNode
{

	int _val;
	ListNode* _next;
	ListNode* _prev;

	//构造函数
	ListNode(int val = 0)
		:_val(val)
		, _next(nullptr)
		, _prev(nullptr)
	{
		cout << "ListNode(int val = 0)" << endl;
	}

	~ListNode()
	{
		cout << "ListNode()" << endl;
	}
};

void f()
{
	// 他的用法跟malloc和free是完全一样的,功能都是在堆上申请释放空间
	// 失败了处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常
	ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
	free(p1);

	ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));
	operator delete(p2);


	void* p3 = malloc(0x7fffffff);
	if (p3 == NULL)
	{
		cout << "malloc fail" << endl;
	}

	void* p4 = operator new(0x7fffffff);

	ListNode* p5 = new ListNode(2);

	cout << "继续" << endl;
}
//
//
int main()
{
	try
	{
		f();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

malloc失败返回NULL,程序还会继续向下执行,但是operator new失败,就会报异常,f函数后面的没有在继续执行,然后走进catch语句中。


5.operator newoperator delete的类函数重载–了解即可

struct ListNode//目的是提高效率
{
	ListNode* _next;
	ListNode* _prev;
	int _val;

	// 类中重载专属operator new
	void* operator new(size_t n)
	{
	void* p = nullptr;
	p = allocator<ListNode>().allocate(1);
	cout << "memory pool allocate" << endl;
	return p;
	}

	void operator delete(void* p)
	{
		allocator<ListNode>().deallocate((ListNode*)p, 1);
		cout << "memory pool deallocate" << endl;

	}

	ListNode(int val)
		:_next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{}
};

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

如果你自己在类里面写了operator newoperator delete,那么编译器就不会去调用系统提供的了,这是一种提高效率的方式。

我们是可以通过反汇编来看效果的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvkeff44-1662721223995)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909162547713.png)]


6.定位new–placement-new

通过上述的学习我们知道,malloc,和operator是不会去调用构造函数的,new会去调用构造函数,而且构造函数是只允许构造出对象时调用,而你的对象创建出来之后是无法调用构造的。

但是如果我们operator new了一块空间(未初始化),但是又想要调用其构造函数,该怎们办呢?

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}

	~A()
	{

		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A* pa = (A*)operator new(sizeof(A));
	//pa->A();//error错误调用方式,构造函数只允许构造时进行调用
	new(pa)A; // 定位new,placement-new,显示调用构造函数初始化这块对象空间

	A* pb = (A*)operator new(sizeof(A));
	new(pb)A(3);
    
    A* pc = new A;
	new(pc)A(3);

	// 等于 delete p
	pa->~A(); // 析构函数可以显示调用
	operator delete(pa);

	pb->~A();
	operator delete(pb);
    
    delete pc;
	return 0;
}

大家要知道定位new哦,他是一种对已经创建出来的对象,还能继续调用其构造函数的方式。


7.malloc/freenew/delete的区别–常考面试题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xeNelsEo-1662721223997)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909163514648.png)]


8.再次理解内存泄漏

首先大家先想一想这个问题:内存泄漏是指针丢了是内存丢了

内存管理中,内存是用指针去维护的,当你的动态内存空间还没有释放时,你已经把指针弄丢了,那么你将无法控制这段空间,进而导致内存泄漏。

什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而 造成了内存的浪费

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会 导致响应越来越慢,最终卡死。


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gxBTy38v-1662721223998)(D:\gitee仓库\博客使用的表情包\给点赞吧.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s5l3clT4-1662721223999)(D:\gitee仓库\博客使用的表情包\要赞.jpg)]

感谢大家的观看!

相关文章:

  • 【微信小程序】自定义组件(二)
  • 基于SSM的水果商城
  • Ubuntu20.04美化成mac OS苹果风格
  • 国际航运管理复习总结题
  • 详解Redis基础数据类型Set增删查(带Java源码)
  • 从零开始实现一个量化回测系统(二)
  • 设计模式:工厂模式
  • 基于Python实现的英文文本信息检索系统
  • java语言入门(一)之JAVA语言基础入门
  • Redis缓存数据库
  • Arm64 linux Virtual memory分析
  • std::cout、printf 和 RCLCPP_INFO的对比
  • 人工智能导论笔记
  • 基于ODBTC有序抖动块截断编码和DCT域数字水印嵌入提取算法matlab仿真
  • 场景文字检测DBnet论文解读
  • 【React系列】如何构建React应用程序
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • Angular 响应式表单 基础例子
  • iOS | NSProxy
  • JavaScript的使用你知道几种?(上)
  • JS基础之数据类型、对象、原型、原型链、继承
  • js数组之filter
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • 从setTimeout-setInterval看JS线程
  • 第2章 网络文档
  • 力扣(LeetCode)22
  • 软件开发学习的5大技巧,你知道吗?
  • 微信小程序设置上一页数据
  • 用jQuery怎么做到前后端分离
  • 自动记录MySQL慢查询快照脚本
  • 自制字幕遮挡器
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (1)(1.13) SiK无线电高级配置(五)
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (26)4.7 字符函数和字符串函数
  • (C++17) std算法之执行策略 execution
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (汇总)os模块以及shutil模块对文件的操作
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • @entity 不限字节长度的类型_一文读懂Redis常见对象类型的底层数据结构
  • @NoArgsConstructor和@AllArgsConstructor,@Builder
  • @Validated和@Valid校验参数区别
  • @基于大模型的旅游路线推荐方案