首页 > 知识百科 > 正文

C/C++内存管理学习【新】原创

文章目录

一、C/C++内存分配二、C语言中动态内存管理方式:malloc/calloc/realloc/free三、C++内存管理方式3.1 new/delete操作输入类型3.2 new和delete操作自定义类型四、operator new与operator delete函数五、new和delete的实现原理5.1输入类型六、定位new表达式(placement-new)七、常见面试题7.1内存缺失7.2 内存泄漏分类 7.3 如何检测内存泄漏 7.4 如何检测内存泄漏

一、C/C++内存分配

在学习之前我们先看一下下面这些代码都分配在哪里?
int globalVar = 1;静态 int staticGlobalVar = 1;void 测试(){静态 int staticVar =  1;int localVar = 1;int num1 [10] = { 1, 2, 3, 4 };字符char2[] = "abcd";const char* pChar3 = "abcd";int* ptr1 =< /span> (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)< /span>calloc(4 sizeof(int));int* ptr3 = ( int*)realloc(ptr2, sizeof(int) * 4);免费(ptr1);< span class="94f6-8ec3-1604-bec3 token function">免费(ptr3);}
选择题:

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?CstaticGlobalVar在哪里?CstaticVar在哪里?ClocalVar在哪里?Anum1 A
char2在哪里?A字符串在常量区,char2会栈上开辟一个数据库,然后将常量区的字符串复制过到栈上,所以结果是A,上题的num1是直接在栈上开辟的,所以相似*char2在哪里?A这上题说了没讲到pChar3在哪里?< strong>A const 修饰是代表常量区,不代表就在常量区,pChar3是栈上的一个变量,指向了常量区的字符串 *pChar3在哪里?D常量区的字符串,解引用就是找常量区的ptr1在哪里?A也是栈上的物理变量*ptr1在哪里?B在堆上开辟的空间
填空题: sizeof(num1) = 40;sizeof(char2) = 5;strlen(char2) = 4;sizeof (pChar3) = 4/8;strlen(pChar3) = 4;sizeof(ptr1) = 4/8; sizeof 和 strlen 区别? 这里具体可以移步到C语言指针章节,最后面有详细的讲解

【说明】

栈又称为堆栈–非静态局部变量/函数参数/返回值等,堆栈是快速增长的。内存映射段是I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。堆用于程序时动态内存分配,堆是可以增长的。数据段–存储全局数据和静态数据。代码段–显示的代码/常见常量。< hr />

为什么要划分这些区域

为了方便管理,程序中有各种不同的数据

这些区域哪个区域是我们需要重点关注的? 堆【堆我们自己自主控制的】

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

void 测试(){int* p1 = (int< span class="8ec3-1604-bec3-5768 token 运算符">*)malloc (sizeof(int));免费(p1< span class="1472-0049-c378-902e token punctuation">);// 1.malloc/calloc/realloc 的区别是什么? int* p2 = (int*)< /span>calloc(4 sizeof(int<跨度类="令牌标点">));int* p3 = (int*)realloc(p2, sizeof(int) * 10);// 这里需要free(p2)吗?免费(p3);}

【面试题】

< p>malloc/calloc/realloc的区别?

malloc是在内存中直接开辟一块空间calloc会在开辟的时候进行初始化生成【初始化0】realloc可以进行已开辟的空间进行扩容

三、 C++内存管理方式

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

3.1 new/delete操作内置类型

void 测试(){//动态申请一个int类型的空间int* ptr4 =  int;//动态申请一个int类型的空间并初始化为10int* ptr5 =  int(10 );//动态申请10个int类型的空间int* ptr6 =  int[3 ];删除 ptr4;删除 ptr5;删除[] ptr6;}

new10个对象进行初始化,后面跟上大逗号进行即可
int* p2 =  int[10] {1  2 3 4 5, 6, 7} ;

这里一定要注意,要匹配着使用

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

3.2 new和删除操作自定义类型

class A {公共:< /span>A(int a = 0) _a(a){cout << < span class="8ec3-1604-bec3-5768 token string">"A():" << 这个 << endl;} ~A(){cout << "~A():"  << 这个 << endl< span class="94f6-8ec3-1604-bec3 token punctuation">;}私有<跨度类s="token 关键字">int _a;} ;int main(){// new/delete 和 malloc/free对于【自定义类型】最大的区别是 new/delete 除了留出空间还会调用构造函数和构造函数A* p1 = (A*)malloc(sizeof(A)); A* p2 =  A( 1);免费(p1);< /span>delete p2;// 内置类型几乎是一样的int* p3 = (int*)< /span>malloc(sizeof(int)) ; // Cint* p4 =  int;免费(p3)< span class="1604-bec3-5768-6382 token punctuation">;删除 p4;A* p5 = (A*)malloc((A) * 10);A* p6 =  A[10];免费(p5);删除[] p6;返回 0;}

小结:在申请自定义类型的空间时,new会调用构造函数,delete会调用构造函数,而malloc与free不会。

在我们使用malloc的时候,都需要手动检查,而new失败了就会抛出异常
struct ListNode{ListNode* _next;int _val;ListNode(int val< span class="8ec3-1604-bec3-5768 token punctuation">):_next (nullptr), _val(val){}};//创建的不带哨兵位ListNode* CreateList( int n){ ListNode head(-1); //哨兵位ListNode* tail = &head;int val;printf("请依次输入%d个节点的值:>", n);对于 (< /span>int i = 0; i < n; i ++){cin >> val;tail->_next =< /跨度> <水疗中心n class="737c-9782-60e2-4d6e token关键字">新 ListNode(val);tail = tail->_next;}返回 head._next;}void func(){int n = 1;同时 ( 1){int* p =  int[1024 * 1024 * 100< /span>];cout << n << "->" << p << endl;++n;}}int  main(){ // 1、使用上,变得简洁了int * p0 = (int*)malloc( sizeof(int));int* p1 =  int;int* p2 =  int[ 10]; // new 10个int对象// 2、可以控制初始化int* p3 =  int< /span>(10); // new 1个int对象,初始化成10int* p4 =  int[< /span>10] { 1, 2 3 4 , 5 };// 3、自定义类型,开空间+构造函数// 4、new失败了以后抛异常,不需要手动检查ListNode* node1 =  ListNode(1);ListNode * 节点2 =  ListNode(2)< span class="1604-bec3-5768-6382 token punctuation">;ListNode* node3 =  ListNode(3< span class="60e2-4d6e-9138-0c6f token punctuation">);//...ListNode* list1 = CreateList(5);删除 p3;删除 [] p4;删除 p1< span class="9138-0c6f-1472-0049 token punctuation">;删除[ ] p2;//抛出异常尝试{func( <跨度类=令牌标点符号">);}catch (const 异常& e){cout << e什么()< /span> << endl;}返回 0;}< /span>

四、operator new与operator delete函数

new和delete是用户进行动态内存和申请释放的操作符,operator new和运算符删除是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。< /p>

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

void * __CRTDECL 运算符 (size_t 大小) _THROW1(_STD bad_alloc){// 尝试分配大小字节void* p;<跨度class="60e2-4d6e-9138-0c6f token 关键字">while ((p  = malloc(大小)) == 0 )if (_callnewh(大小) ==  0){/ /报告没有内存//如果申请内存失败了,这里会抛出bad_alloc类型异常static <水疗中心n class="8ec3-1604-bec3-5768 token 关键字">const std::bad_alloc nomem;_RAISE(nomem) ;}返回 (p);}/ *operator delete:函数最终是通过free来释放空间的*/void operator 删除(void*  pUserData){_CrtMemBlockHeader* pHead;RTCCALLBACK (_RTC_Free_hook (pUserData < span class="94f6-8ec3-1604-bec3 token number">0)) ;if (pUserData == < span class="737c-9782-60e2-4d6e token 常量">NULL)返回 ;_mlock(_HEAP_LOCK); /* 阻止其他线程 */__TRY/* 获取内存块头的指针 */pHead = pHdr(pUserData);/* 验证区块类型 */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse)< /span>;__FINALLY_munlock(_HEAP_LOCK); /* 释放其他线程 */__END_TRY_FINALLYreturn; }/*免费的实现*/#定义 免费(p) _free_dbg(p, _NORMAL_BLOCK) 

通过上述两个全局函数的实现知道,operator new实际也是通过malloc来申请空间,如果malloc申请空间就成功直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续,否则就申请抛出异常。操作员删除最终是通过释放来释放空间的。

<我们通过设置也可以看到,这些最后还是分别调用malloc和free的

operator new对malloc的封装,失败抛异常实现newoperator删除对免费的封装
class 堆栈{public堆栈(){_a = (int*)malloc(sizeof(int) * 4);_top < span class="6382-2d8b-737c-9782 token 操作符">= 0;_capacity = 4;}~堆栈() {免费(< /span>_a);_top = _capacity = 0;}私有:int* _a;int _top;int _capacity;};int main( ){A* ptr1 =  A; //打开rator new + 1次构造A* ptr2 = new  A[10]; //运算符new[] + 10次构造删除 ptr1< span class="60e2-4d6e-9138-0c6f token punctuation">; // 1次解析结构 + 运算符删除删除< span class="0c6f-1472-0049-c378 token punctuation">[] ptr2; // 10次解析构 + 运算符删除[]堆栈* pst = 新建堆栈;删除 pst;返回 0; }
删除是先解析结构再释放这块空间
现在在使用自定义类型进行new的时候会在头部多开4个字节存储个数,这就是为什么在删除的时候要匹配着加[],还需要调用解析结构函数,这样就知道要释放了

通过内存监视窗口再看一下

如果不显示写这个解析结构函数,就也不会多开4个字节,编译器会自动生成一个,编译器觉得什么也干,自动就会优化掉了也,不会调用了

而内置类型就不会多开,内置类型就不用调用解析构造函数

p>

五、new和delete的实现原理

5.1内置类型

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

5.2 自定义类型

新的原理

调用操作符new函数申请空间,在申请的空间上执行构造函数,完成对象的构造

删除原理

在空间上执行结构函数,对象中资源的清理工作调用operator删除函数释放对象的空间

new T[N]的原理

调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请在申请的空间上执行N次构造函数

delete[]的原理

在释放的对象空间上执行N次构造函数,完成N个对象中资源的清理调用operator delete[]释放空间,实际在operator delete[]中调用操作符删除来释放空间

六、定位新表达式(placement-new)

定位新表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用新的定义表达式进行显示调整构造函数进行初始化。
<代码 class="4d6e-9138-0c6f-1472 prism language-cpp">class A{ public:A< /span>(int a = 0): _a(a){< !-- -->cout << "A():" << 这个 << endl; }~A(){cout << "~A():" <<  << endl;} 私有:int _a;};//定位新/替换新 int main(){// p1现在指向的只是和A对象大小相同的一段空间,还不能有一个对象,因为构造函数没有执行A* p1 = < span class="9138-0c6f-1472-0049 token punctuation">(A*)malloc(sizeof(A< span class="bec3-5768-6382-2d8b token punctuation">));( p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参p1->~A();免费(p1);A* p2 = (< /span>A*)运算符 (sizeof(A ));(p2)A( 10);p2->~A( );运算符 删除(p2);< /span>返回 0;} 

七、常见面试题

malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是:从堆上申请空间,并且需要用户手动释放。不同的地方是:

malloc和free是函数,new和delete是操作符malloc申请的空间不会初始化,new初始化malloc申请空间时,需要手动计算空间大小并提交,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可malloc的返回值为void*,使用时必须强转, new不需要,new后面跟的是空间类型的因为malloc申请空间失败时,是返回NULL,使用时必须判空,new不需要,但是new需要捕获异常因此自定义类型对象时,malloc/free只会开辟完成空间,不会调用构造函数与解析构造函数,而new在申请空间后会调用构造函数对象的初始化,在释放空间前删除会调用构造函数完成空间中资源的清理

7.1 内存内存泄漏

什么是内存泄漏,内存泄漏的残害什么是内存泄漏:内存泄漏是指因为疏忽或错误程序导致未能释放已经不再使用的内存的情况。 内存泄漏并不是指内存在物理上的消失,但是应用程序分配了某段内存后,由于设计错误,失去了该段内存的控制,从而造成了内存的浪费。 内存泄漏的浪费:长期运行的程序出现内存泄漏,影响很大,如网络、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
void 内存泄漏(){// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof (int));int* p2 < span 类 =“令牌运算符”>=  int;// 2.异常安全问题int* p3 =  int[10];Func(); //这里Func函数抛出异常导致delete[] p3未执行,p3没有被释放。删除[] p3;} 

7.2 内存内存泄漏分类

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

堆内存泄漏(Heap Leak) 堆内存指的是程序执行中须要通过malloc / calloc分配/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用系统资源浪费是指程序使用系统分配的资源,比方设备、文件传输、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重的可能导致系统无法减少,

7.3 如何检测内存泄漏

在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks()函数进行简单检测,该函数只报告了大概泄漏了多少个字节,没有其他更准确的位置信息
int main (){< span class="0c6f-1472-0049-c378 token 关键字">int* p = new int[10];// 调用函数放在主函数之后,每次程序退出的时候就会检测是否存在内存泄漏_CrtDumpMemoryLeaks();返回 0;}//程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置检测到内存泄漏转储对象->{79} 正常区块位于 0x00EC5FB8 40  字节 数据 < > CD CD CD CD CD CD CD CD CD CD CD CD CD CDObject 转储完成
因此写代码的时候一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。

7.4 如何避免内存泄漏

工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,即使注意释放了,还是可能会出问题。需要下一条智能修剪来管理一致保证。采用RAII思想或者智能卸载器来管理资源。有些公司内部规范使用内部内置的内存管理库。这库自带内存泄漏检测的功能选项。出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

内存泄漏非常常见,解决方案分为两大类:
1、事前预防型。如智能指针错等。
2、事后查型。如漏检测工具。

最后一篇文章到这里就结束了,感谢大家的收看,请多多指点~

C/C++内存管理学习【新】原创由知识百科栏目发布,感谢您对的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“C/C++内存管理学习【新】原创