欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > C/C++(三)C/C++内存管理

C/C++(三)C/C++内存管理

2025/9/27 15:12:17 来源:https://blog.csdn.net/m0_73682725/article/details/143057430  浏览:    关键词:C/C++(三)C/C++内存管理

这一章会详细的介绍C/C++的内存管理机制,从最底层了解C/C++。

一、C/C++的内存分布(重点)

通过上图可以看出,在C/C++中用户可以操作的内存区域分为五块:栈、内存映射段、堆、数据段、代码段。下面我们详细介绍每一段的作用:

C/C++程序内存分布图和示例

1、

栈,又名堆栈,是一段向下增长的内存区域,大小通常不大,一般在几 MB 到几十 MB 之间。

函数调用建立函数栈帧非静态局部变量 / 函数参数 / 返回值的存储指针变量的存储(指针变量本身存在栈上,其指向的数据不一定在栈上,比如如果其指向的对象是开辟了一段新的内存空间,这个对象就在堆上),都是在栈上。

2、内存映射段

高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。(这个主要在Linux系统编程中用到,我们不在C / C++专栏多做赘述)

3、堆

是一段向上增长的内存区域,大小通常较大,可以达到几百 MB 到几 GB,甚至更大。

堆主要用于在程序运行时分配动态内存

4、数据段(静态区)

主要用于存储全局数据和静态数据

5、代码段(常量区)

主要用于储存可执行代码和只读常量

二、C语言的动态内存管理方式:malloc / calloc / realloc / free

1、malloc

void* malloc(size_t size);

malloc 函数向内存申请一块连续可用的空间(空间中的值都是随机值),并返回指向这块空间的指针。

如果开辟成功,则返回一个指向开辟好空间的指针。

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查且不可过大。

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定(即强转)

如果参数 size 0malloc的行为是标准是未定义的,取决于编译器。

// 使用举例,开辟可以存放10个整型的空间
int *p = (int*) malloc(sizeof(int) * 10);    

2、calloc

void* calloc(size_t num, size_t size);

函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0;与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

// 使用举例,可以存放10个整型的空间
int* p = (int*)calloc(10, sizeof(int));

3、realloc

realloc函数的作用是更加灵活地管理动态内存。

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,为了合理的使用内存,我们需要对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

void* realloc(void* ptr, size_t size);

ptr 是要调整的内存地址

size 为调整之后新大小

返回值为调整之后的内存起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间

如果内存是变小,会丢弃部分数据;如果变大,且现有内存区域后面也有足够的未使用内存,可能会原地扩容减小消耗;如果没有,则异地扩容,把数据拷贝过去并自动释放原有内存空间。

4、free

void* free(void* ptr)

free函数用来释放动态开辟的内存。

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr NULL指针,则函数什么事都不做

5、malloc / calloc / realloc 三者的区别是什么?

malloc 开辟的空间不会初始化,都是随机值。

calloc 会把开辟的空间全部初始化成0。

realloc则是灵活管理动态内存大小,小了会丢弃,大了视情况选择原地扩容(现有内存区域后面也有足够的未使用内存) / 异地扩容(把数据拷贝过去并自动释放原有内存空间)。

三、C++的动态内存管理方式:new / delete(重点)

new 和 delete是C++自己的内存管理方式,相较于C语言的 malloc / free 更加简易

1、new / delete 定义内置类型 / 自定义类型

直接代码说明:

#include <iostream>
#include <cstdlib>  // 包含 malloc 和 free 的头文件
using namespace std;class A
{
public:A(int a = 0) : _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{//  new / delete 和 malloc / free 在为内置类型开辟空间时区别不大int* p1 = (int*)malloc(sizeof(int));if (p1 == nullptr) {cout << "malloc 分配失败" << endl;}else {cout << "malloc 分配的内存地址: " << p1 << endl;*p1 = 10;  // 初始化分配的内存cout << "p1 的值: " << *p1 << endl;free(p1);cout << "内存已由 free 释放" << endl;}int* p2 = new int;if (p2 == nullptr) {cout << "new 分配失败" << endl;}else {cout << "new 分配的内存地址: " << p2 << endl;*p2 = 20;  // 初始化分配的内存cout << "p2 的值: " << *p2 << endl;delete p2;cout << "内存已由 delete 释放" << endl;}cout << endl;// new / delete 和 malloc / free的最大区别其实是在为自定义类型开辟空间的时候,new / delete会调用构造 / 析构函数,malloc / free则不会A* p3 = new A(1);delete p3;A* p4 = (A*)malloc(sizeof(A));free(p4);return 0;
}

根据运行结果我们可以发现,new / delete 和 malloc / free 在为内置类型开辟空间时区别不大;new / delete 和 malloc / free的最大区别其实是在为自定义类型开辟空间的时候,new / delete会调用构造 / 析构函数,malloc / free则不会。

2、new / delete 的底层实现原理

new / delete 的底层实现原理其实是 operator new + 构造函数 / operator delete + 析构函数

2.1  operator new / operator delete

operator new 和 operator delete 并不是 new 和 delete 的函数重载,而是系统提供的全局函数。

new在底层通过调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。(PS:二者的底层实际也都是通过 malloc 和 free 来申请和释放的)

/*operator new:该函数实际通过 malloc 来申请空间,当 malloc 申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果该应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// 尝试分配 size 字节的内存void *p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// 报告没有内存// 如果申请内存失败了,这里会抛出 bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}/*operator delete: 该函数最终是通过 free 来释放空间的
*/
void operator delete(void *pUserData)
{_CrtMemBlockHeader * pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData == NULL)return;_mlock(_HEAP_LOCK);  // 阻塞其他线程__TRY// 获取内存块头指针pHead = pHdr(pUserData);// 验证块类型_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse);__FINALLY_munlock(_HEAP_LOCK);  // 释放其他线程__END_TRY_FINALLYreturn;
}/*free 的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过上述两个全局函数的实现知道,operator new 实际也是通过 malloc 来申请空间,如果

malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过 free 来释放空间的

2.2  实现原理总结

2.2.1  内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似。

不同的地方是: new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc则会返回NULL。

2.2.2 自定义类型

new的原理

1、调用operator new函数申请空间。

2、在申请的空间上执行构造函数,完成对象的构造。

delete的原理

1、在空间上执行析构函数,完成对象中资源的清理工作。

2、调用operator delete函数释放对象的空间。

new T[N]的原理

1、调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对

     象空间的申请。

2、在申请的空间上执行N次构造函数。

delete[]的原理

1、在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。

2、调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释

     放空间。

 四、malloc / free 和  new / delete 的区别(重点)

用法方面:

1、malloc / free 是函数;new / delete 是操作符。

2、malloc 不会为开辟的空间初始化值;new 可以初始化。

3、malloc 的返回值的 void* ,开辟空间必须强转为对应类型;new 不需要强转,后面跟的         就是类型。

4、malloc 申请空间的时候,需要手动计算空间并传递;new 只需要传递类型,new[]也只需        要在 [] 里面填入指定对象的个数即可。

5、new 在开辟失败后,会返回NULL,所以使用malloc必须判空;new不会返回NULL,不         会判空,但会抛异常,因此使用new必须捕获异常

底层原理方面:

在申请自定义对象类型时,malloc / free只会开辟 / 释放空间;而 new / delete会在开辟 / 释放空间的同时,调用构造 / 析构函数完成对象的初始化 / 资源的清理。

五、内存泄漏问题

1、内存泄漏的分类

C/C++程序中一般我们关心两种方面的内存泄漏:

堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一

块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分

内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放

掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2、如何检测内存泄漏(仅供了解) 

在VS下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,但是该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息

int main()
{int* p = new int[10];// 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏_CrtDumpMemoryLeaks();return 0;
}// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位;但如果工程比较大,内存泄漏位置比较多,不太好查时,一般都是借助第三方内存泄漏检测工具处理的。

Linux 下内存泄漏检测:Linux 下的几款内存检测工具

在windows下使用第三方工具:VLD 工具

其他工具:内存泄漏工具比较

3、如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。(PS:即便是理想状态。如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一              条智能指针来管理才有保证。)

2. 采用RAII思想或者智能指针来管理资源。

3. 有些公司内部规范可能会使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功      能选项。

4. 出问题了使用内存泄漏工具检测。(PS:不过很多工具都不够靠谱,或者收费昂贵。)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词