• C/C++内存管理


    C/C++内存分布

    对于C/C++计算机是这样分配内存的:
    在这里插入图片描述
    计算为什么要分配就像国内的某个省里面有很多地区,不同的地区做不同的事情。
    C/C++内存区域划分:
    先来看这段代码,这些数据都是储存在哪里的。

    int a = 0;
    static int b = 10;
    int test()
    {
    	int c = 20;
    	static int d = 10;
    	int arr[3] = { 0,1,2 };
    	char cha1[] = "abcd";
    	const char* cha2 = "abcd";
    	int* p1 = (int*)malloc(4 * sizeof(int));
    	int* p2 = (int*)calloc(4 * sizeof(int), 0);
    	int* p3 = (int*)realloc(p2, 4 * sizeof(int));
    	free(p1);
    	free(p3);
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    黑色的框是在数据段,酒红色的框在栈中,黄色的在代码段,蓝色的在堆上。
    首先想一下,我们表面上写的代码,每一个字符在没运行的时候是储存在哪里的?是在磁盘里的,因为我们可以找到编译之后生成的exe文件。
    当运行起来的时候,所有代码都转化成了汇编,再转化成为了0和1,然后程序就变成了进程,最后在内存里开一片虚拟进程地址空间。(也就是我们看到的空间编号,例如0x00000000就是空指针
    不同的数据存在不同的区域,那么,我们运行的过程中是一行一行代码运行的,而不是一起运行的,那么写的这些代码就需要放到内存里等待执行,那么放在哪里呢?是放在了代码段。
    代码段只读是因为如果可以写,执行代码的过程中电脑被恶意攻击,修改你的代码放进病毒,那么后果不堪设想。
    在代码执行的过程中,存入数据段和代码段的是直接存入,而栈和堆不一样,谁用去开辟谁,不用就释放掉。
    内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
    内核空间是操作系统调用各个进程的代码。

    C语言中动态内存管理方式

    这里是C语言的动态内存:
    C语言动态内存管理

    C++中动态内存管理

    new与delete

    在C++中对于动态内存管理也进行了一定的优化,C++讲究的是封装。
    注意,C++中内存管理的是两个操作符(也是关键字)new与delete。

    #include 
    using namespace std;
    
    int main()
    {
    	int* p = new int;//开辟一个类型为int的动态空间
    	delete p;//释放掉这块内存
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    显然,没有初始化,这里就跟malloc和free一样。
    如果你想初始化或者是开辟多个空间也是可以的:

    #include 
    using namespace std;
    
    int main()
    {
    	int* p1 = new int(10);//初始化这块空间的值是10
    	int* p2 = new int[10];//开辟10个int类型的空间不初始化
    	int* p3 = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//开辟10个int类型的空间,花括号里面你得是初始化数值
    	delete p1;
    	delete[] p2;
    	delete[] p3;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],匹配起来使用,如果不匹配有可能会报错。

    对于自定义类型的动态管理

    new和delete除了比malloc和free写的时候方便一点,还有就是经典的对于自定义类型有很大的作用。
    new会调用该类的构造函数,delete会去调用该类的析构函数。

    #include 
    using namespace std;
    class N
    {
    public:
    	N()
    		:_a(10)
    	{
    		cout << "构造" << endl;
    	}
    	~N()
    	{
    		_a = 0;
    		cout << "析构" << endl;
    	}
    	void print()
    	{
    		cout << _a << endl;
    	}
    private:
    	int _a;
    };
    int main()
    {
    	N* p = new N;//这里不需要强制类型转化,因为后面就是类型
    	p->print();
    	delete p;
    }
    
    • 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

    在这里插入图片描述
    如果new开辟失败返回的不是空指针,而是抛异常。

    #include 
    using namespace std;
    
    int main()
    {
    
    	try
    	{
    		while (1)
    		{
    			int* p = new int[1000 * 1024];
    		}
    	}
    	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

    在这里插入图片描述

    operator new与operator delete函数

    new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
    系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
    operator delete全局函数来释放空间。
    注意,这里不是对于new和delete进行重载
    这里是operator new与operator delete的库函数:

    void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)//其实就相当于对malloc进行了封装
    {
    	// try to allocate size bytes
    	void* p;
    	while ((p = malloc(size)) == 0)//如果等于空,下面就是抛异常的操作
    		if (_callnewh(size) == 0)
    		{
    			// report no memory
    			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
    			static const std::bad_alloc nomem;
    			_RAISE(nomem);
    		}
    	return (p);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
    失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

    void operator delete(void* pUserData)
    {
    	_CrtMemBlockHeader* pHead;
    	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    	if (pUserData == NULL)
    		return;
    	_mlock(_HEAP_LOCK); /* block other threads */
    	__TRY
    		/* get a pointer to memory block header */
    		pHead = pHdr(pUserData);
    	/* verify block type */
    	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    	_free_dbg(pUserData, pHead->nBlockUse);
    	__FINALLY
    		_munlock(_HEAP_LOCK); /* release other threads */
    	__END_TRY_FINALLY
    		return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    operator delete: 该函数最终是通过free来释放空间的。

    #define free(p) _free_dbg(p, _NORMAL_BLOCK)
    
    • 1

    C语言中free的实现。

    new和delete的实现原理

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

    1. new的原理:先调用operator new函数申请空间,再去申请的空间上执行构造函数,完成对象的构造。
    2. delete的原理:先在空间上执行析构函数,完成对象中资源的清理工作,再去调用operator delete函数释放对象的空间。
    3. new T[N]的原理:先调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,再在申请的空间上执行N次构造函数。
    4. delete[]的原理:先在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理,再调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。

    定位new

    定位new的作用:对于已经分配的空间进行显式调用构造函数初始化。
    语法:new(分配空间的位置)构造函数;

    #include 
    using namespace std;
    class N
    {
    public:
    	N()
    		:_a(10)
    	{ }
    	~N()
    	{
    		_a = 0;
    	}
    private:
    	int _a;
    };
    int main()
    {
    	N* p = (N*)malloc(sizeof(N));//这里不会对p进行初始化,只是分配N类大小的地址
    	if (p == NULL)
    	{
    		perror("malloc error");
    		exit(-1);
    	}
    	new(p)N();
    	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

    在这里插入图片描述
    那么会有人问这里为什么不一开始就用new进行初始化呢?现在的操作不是多此一举吗?
    因为定位new是能提高效率的。
    池化技术——提高效率:
    在这里插入图片描述
    蓝色的地方就是开了一内存池,专门对某地方有作用,当然从内存池里面拿出来的也要进行初始化,这时候定位new就有了效果。

    malloc/free和new/delete的区别

    共同点:都是从堆上申请空间,程序没跑完之前是不会自动释放内存的,需要自己手动释放内存。
    不同点:

    1. malloc和free是函数;new和delete是关键字。
    2. malloc只会开辟对应类型大小的空间,并不会进行初始化操作;new后面添加一个(),里面放入你想初始化的值,如果是多个元素,就在后面加{}然后里面放入N个值,先开辟对应类型的空间,之后会将在()或{}里的值存入。
    3. malloc返回的时候是void*,需要强制类型转换;new后面的就是空间类型。
    4. malloc开辟空间的时候需要我们计算大小然后传递;new如果想开辟连续的空间后面加[],里面添加个数即可。
    5. malloc开辟空间返回的是空指针,需要判断是否为空;new是返回一个异常,需要进行异常捕获。
    6. 对于自定义类型而言,malloc和free开辟或释放一个该类型大小的空间,之后就没有后续操作了;而new会先开辟该类型大小的空间之后再调用构造函数进行初始化,delete会先调用析构函数进行资源清理然后再释放掉这块内存空间。

    内存泄漏

    什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
    存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
    该段内存的控制,因而造成了内存的浪费。(指针丢了,不然还是可以进行内存释放的)
    内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
    内存泄漏会导致响应越来越慢,最终卡死。
    **内存泄漏分为两种:**一种是忘记释放内存,另一种是写了释放内存的操作,因为别的原因没有进行这个操作。
    在linux下内存泄漏检测:linux下几款内存泄漏检测工具
    一定要重视内存泄露!!!!!!

  • 相关阅读:
    基础IO —— Linux
    第十七章 管理组件库的pull request
    浏览器工作原理分析与首屏加载
    若依开源框架
    go context 包
    VUE路由案例(商品列表)---vue练习必选项目(附原码)
    ios 使用runtime实现自动解归档
    Windows中控制台(cmd)模式下运行程序卡死/挂起现象解决方案(快速编辑模式)
    机器人虚拟仿真工作站考试
    【C++/类和对象/2023年10月3日】
  • 原文地址:https://blog.csdn.net/qq_63580639/article/details/127342315