• c++图解内存管理


    c&c++内存管理

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

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


    验证栈是向下生长的
    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    一般情况下,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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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


    开辟自定义类型的空间(请用vs2013以上版本测试代码)
    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    ​ 学过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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如果你自己在类里面写了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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    大家要知道定位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)]

    感谢大家的观看!

  • 相关阅读:
    学习笔记14--机器学习在局部路径规划中的应用
    七夕情人节送女朋友什么礼物?七夕情人节礼物推荐
    帆船动力学仿真分析
    数据结构学习笔记(第一章绪论)
    Himall商城Web帮助类获得请求客户端的操作系统名称、判断是否是浏览器请求、是否是移动设备请求、判断是否是搜索引擎爬虫请求
    Django测试环境搭建及ORM查询(创建外键|夸表查询|双下划线查询)
    C#实现FFT(递归法)
    【短道速滑九】仿halcon中gauss_filter小半径高斯模糊优化的实现
    2024三掌柜赠书活动第十三期:API安全技术与实战
    js 监控 浏览器关闭前 调用
  • 原文地址:https://blog.csdn.net/m0_64361907/article/details/126788057