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);}
选择题: globalVar在哪里?CstaticGlobalVar在哪里?CstaticVar在哪里?ClocalVar在哪里?Anum1 A选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
【说明】
栈又称为堆栈–非静态局部变量/函数参数/返回值等,堆栈是快速增长的。内存映射段是I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。堆用于程序时动态内存分配,堆是可以增长的。数据段–存储全局数据和静态数据。代码段–显示的代码/常见常量。< hr />为什么要划分这些区域
为了方便管理,程序中有各种不同的数据这些区域哪个区域是我们需要重点关注的? p> 堆【堆我们自己自主控制的】
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 span>* p3 = (int*)realloc(p2, sizeof(int) * 10);// 这里需要free(p2)吗?免费(p3);}
【面试题】
< p>malloc/calloc/realloc的区别? malloc是在内存中直接开辟一块空间calloc会在开辟的时候进行初始化生成【初始化0】realloc可以进行已开辟的空间进行扩容void 测试(){//动态申请一个int类型的空间int* ptr4 = 新 int span>;//动态申请一个int类型的空间并初始化为10int* ptr5 = 新 int(10 );//动态申请10个int类型的空间int* ptr6 = 新 int[3 ];删除 ptr4;删除 ptr5;删除[] ptr6;} span>
new10个对象进行初始化,后面跟上大逗号进行即可 int* p2 = 新 int[10] {1 , 2, 3, 4, 5, 6, 7} ;
这里一定要注意,要匹配着使用注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。
class A {公共:< /span>A(int a = 0): span> _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* span> p6 = 新 A[10];免费 span>(p5);删除[] p6; span>返回 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); //哨兵位 span>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 span> 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 span>* p1 = 新 int;int* p2 = 新 int[ 10]; // new 10个int对象// 2、可以控制初始化int* p3 = 新 int< /span>(10); // new 1个int对象,初始化成10int* p4 = 新 int[< /span>10] { span> 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>
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 span>(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; }
删除是先解析结构再释放这块空间 []
,还需要调用解析结构函数,这样就知道要释放了 通过内存监视窗口再看一下 如果不显示写这个解析结构函数,就也不会多开4个字节,编译器会自动生成一个,编译器觉得什么也干,自动就会优化掉了也,不会调用了
而内置类型就不会多开,内置类型就不用调用解析构造函数
p>5.2 自定义类型
新的原理
调用操作符new函数申请空间,在申请的空间上执行构造函数,完成对象的构造删除原理
在空间上执行结构函数,对象中资源的清理工作调用operator删除函数释放对象的空间new T[N]的原理
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请在申请的空间上执行N次构造函数delete[]的原理
在释放的对象空间上执行N次构造函数,完成N个对象中资源的清理调用operator delete[]释放空间,实际在operator delete[]中调用操作符删除来释放空间使用格式:
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; span>};//定位新/替换新 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(); span>免费(p1);A* p2 = (< /span>A*)运算符 新(sizeof(A ));新(p2)A( 10);p2->~A( );运算符 删除(p2);< /span>返回 0;}
malloc/free和new/delete的共同点是:都是:从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc和free是函数,new和delete是操作符malloc申请的空间不会初始化,new初始化malloc申请空间时,需要手动计算空间大小并提交,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可malloc的返回值为void*,使用时必须强转, new不需要,new后面跟的是空间类型的因为malloc申请空间失败时,是返回NULL,使用时必须判空,new不需要,但是new需要捕获异常因此自定义类型对象时,malloc/free只会开辟完成空间,不会调用构造函数与解析构造函数,而new在申请空间后会调用构造函数对象的初始化,在释放空间前删除会调用构造函数完成空间中资源的清理void 内存泄漏( span>){// 1.内存申请了忘记释放int* p1 = span> (int*)malloc(sizeof (int));int* p2 < span 类 =“令牌运算符”>= 新 int;// 2.异常安全问题int* p3 = 新 int[ span>10];Func(); //这里Func函数抛出异常导致delete[] p3未执行,p3没有被释放。删除[] p3;}
C/C++程序中一般我们关心两个方面的内存泄漏:
堆内存泄漏(Heap Leak) 堆内存指的是程序执行中须要通过malloc / calloc分配/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用系统资源浪费是指程序使用系统分配的资源,比方设备、文件传输、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重的可能导致系统无法减少,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 转储完成。 span>
因此写代码的时候一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。 总结一下:
内存泄漏非常常见,解决方案分为两大类:
1、事前预防型。如智能指针错等。
2、事后查型。如漏检测工具。
最后一篇文章到这里就结束了,感谢大家的收看,请多多指点~ p>
C/C++内存管理学习【新】原创由知识百科栏目发布,感谢您对的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“C/C++内存管理学习【新】原创”