尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
【C++】 初阶篇 动态内存管理
前言本文围绕 C/C 内存管理核心知识点从分区、分配机制、实战对比到避坑技巧帮开发者系统性掌握精髓规避内存泄漏等常见问题。一、C/C 内存分布1.1 介绍在 C 语言阶段我们常说局部变量存储在栈区动态内存中的数据存储在堆区静态变量存储在静态区常量存储在常量区其实这里我们所说的栈区、堆区、静态区以及常量区都是虚拟进程地址空间的一部分其中具体内存区域的划分如下栈又叫堆栈 —— 非静态局部变量/函数参数/返回值等等栈是向下增长的先使用高地址的内存。内存映射段是高效的 I/O 映射方式用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存做进程间通信。现在只需要了解一下后续 Linux 会讲到堆用于程序运行时动态内存分配堆是可以向上增长的先使用低地址的内存。数据段 —— 存储全局数据和静态数据。代码段 —— 可执行的代码逻辑代码/只读常量。注虚拟进程地址空间、多进程多线程以及操作系统内核等相关知识我们会在 Linux 中进行学习这里我们只需要学习 C/C 的内存区域划分即可。1.2 题目在学习了 C/C 内存区域的划分后我们来做几道题巩固一下intglobalVar1;staticintstaticGlobalVar1;voidTest(){staticintstaticVar1;intlocalVar1;intnum1[10]{1,2,3,4};charchar2[]abcd;constchar*pChar3abcd;int*ptr1(int*)malloc(sizeof(int)*4);int*ptr2(int*)calloc(4,sizeof(int));int*ptr3(int*)realloc(ptr2,sizeof(int)*4);free(ptr1);free(ptr3);}选项:A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)globalVar 在哪里CstaticGlobalVar 在哪里CstaticVar 在哪里ClocalVar 在哪里Anum1 在哪里A【分析】:globalVar 全局变量在数据段staticGlobalVar 静态全局变量在静态区staticVar 静态局部变量在静态区localVar 局部变量在栈区num1 局部变量在栈区char2 在哪里A*char2 在哪里ApChar3 在哪里A*pChar3 在哪里Dptr1 在哪里A*ptr1 在哪里B【分析】:char2 局部变量在栈区char2 是一个数组把后面常量串拷贝过来到数组中数组在栈上所以 *char2 在栈上pChar3 局部变量在栈区*pChar3 得到的是字符串常量字符在代码段ptr1 局部变量在栈区*ptr1 得到的是动态申请空间的数据在堆区填空sizeof(num1)40; // 数组大小10 个整型数据一共 40 字节sizeof(char2)5; // 包括 \0 的空间strlen(char2)4; // 不包括 \0 的长度sizeof(pChar3)4; // pChar3 为指针strlen(pChar3)4; // 字符串 “abcd” 的长度不包括 \0 的长度sizeof(ptr1)4; // ptr1 是指针简答题malloc/calloc/realloc的区别malloc用于开辟一块动态内存使用时需要指定开辟的空间大小(字节)如果开辟成功返回空间的起始地址如果开辟失败返回NULL且不会初始化calloc的用法和malloc类似只是它有两个参数第一个参数为元素个数第二个参数为每个元素的大小并且它会将该空间中的数据全部初始化为 0realloc用于空间的扩容/缩容它有两个参数第一个参数为需要调整的动态内存的起始地址第二个参数为调整后的空间大小如果第一个参数为NULL则它等价于malloc如果扩容编译器会检查原空间后是否有足够的空间如果足够就直接扩容并返回原空间的起始地址如果不够就新开辟一块空间然后将原空间的数据拷贝到新空间并返回新空间的地址最后再释放原空间如果缩容编译器会直接新开辟一块空间然后拷贝原空间数据到新空间并返回新空间的地址再释放原空间。这里需要free(p2)吗voidTest(){int*p2(int*)calloc(4,sizeof(int));int*p3(int*)realloc(p2,sizeof(int)*10);free(p3);}不需要free(p2)无论是异地扩容还是原地扩容最终都不需要专门释放p2如果是原地扩容p2和p3指向同一块空间如果是异地的话将原内容拷贝后会自动释放原来p2指向的空间栈与堆对比特性栈区 (Stack)堆区 (Heap)管理方式编译器自动分配释放程序员手动通过new/delete等接口管理空间大小较小 (几 MB)较大 (受系统内存限制)访问速度极快 (栈指针操作)较慢 (需内存寻址)生命周期随作用域自动销毁显式释放前持续存在内存碎片无频繁分配释放易产生碎片增长方向向下增长向上增长典型场景局部变量、函数参数大对象、跨作用域数据风险点栈溢出 (过大局部变量)内存泄漏未释放、野指针二、C 动态内存管理方式2.1 引入C 兼容 C 语言所以 C 语言的内存管理方式在 C 中可以继续使用但由于其使用起来比较麻烦且有些地方无能为力因此 C 又提出了自己的内存管理方式通过new和delete操作符进行动态内存管理。2.2 C/C 的使用1. C 语言malloc/calloc/realloc与freemalloc(size_t size)分配指定字节数的堆内存返回void *指针需手动类型转换。calloc(size_t num, size_t size)分配num个大小为size的连续内存初始化为 0。realloc(void* ptr, size_t size)调整已分配内存的大小可能会移动内存块。free(void* ptr)释放通过上述函数分配的堆内存ptr必须指向堆内存起始地址。#includestdio.h#includestdlib.hintmain(){// 1. malloc分配内存不初始化int*m(int*)malloc(2*sizeof(int));m[0]1;m[1]2;printf(malloc: %d, %d\n,m[0],m[1]);// 2. calloc分配并初始化为0int*c(int*)calloc(2,sizeof(int));printf(calloc: %d, %d\n,c[0],c[1]);// 3. realloc扩容malloc的内存m(int*)realloc(m,3*sizeof(int));m[2]3;printf(realloc后: %d, %d, %d\n,m[0],m[1],m[2]);// 4. free释放内存free(m);free(c);return0;}2. Cnew/delete与new[]/delete[]voidTest(){//申请单个空间不初始化int*p1newint;//申请单个空间并初始化int*p2newint(10);//申请连续空间不初始化int*p3newint[10];//申请连续空间并初始化int*p4newint[10]{1,2,3,4,5};//释放单个空间deletep1;deletep2;//释放多个空间delete[]p3;delete[]p4;}注C 不支持扩容要扩容都是自己开辟新空间、拷贝数据然后再销毁原空间。三、区别3.1 new/delete 操作内置类型对于内置类型C 语言和 C 内存管理方式没有明显区别只是 C 中使用new操作符来替代 C 语言中的malloc/calloc函数使用delete操作符来替代free函数同时由于new和delete是操作符/关键字而不是函数所以它们后面不需要跟括号而是直接跟类型即可另外new可以在开辟空间的同时进行初始化。3.2 new/delete 操作自定义类型C 动态内存管理和 C 语言动态内存管理最大的不同在于二者对自定义类型的处理C 语言malloc/calloc/realloc函数只负责开辟空间free函数只负责销毁空间而 C 在申请自定义类型的空间时new会调用构造函数delete会调用析构函数。classA{public:A(inta0):_a(a){coutA 构造thisendl;}~A(){coutA 析构thisendl;}private:int_a;};intmain(){A*p1newA(1);deletep1;coutendl;A*p2newA[2];//生成2个A对象delete[]p2;return0;}newT分配单个 T 类型对象的内存自动调用构造函数初始化。newT[n]分配 n 个 T 类型对象的数组内存自动调用每个对象的构造函数。deleteptr自动调用析构函数释放单个对象内存。delete[]ptr自动调用每个对象的析构函数释放数组内存。注意接口必须成对使用new对应deletenew[]对应delete[]混用会导致内存泄漏或程序崩溃。无需手动类型转换new返回对应类型指针且分配失败时抛出bad_alloc异常而非返回NULL。四、深入 new 与 delete4.1 引入 operator new 和 operator delete在 C 中new和delete是用户进行动态内存申请和释放的操作符operator new和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间delete在底层通过operator delete全局函数来释放空间。需要特别注意的是operator new和operator delete函数不是运算符重载因为它们的参数没有自定义类型而是库里面实现的全局函数仅仅是将它们取名为operator而已很多 C 的初学者都会被二者的函数名所误导。4.2 源码区别C 底层的operator new和operator delete函数如下// operator new// 该函数实际通过malloc来申请空间当malloc申请空间成功时直接返回// 申请空间失败尝试执行空间不足应对措施如果用户设置了应对措施则继续申请否则抛异常。void*__CRTDECLoperatornew(size_t size)_THROW1(_STD bad_alloc){// try to allocate size bytesvoid*p;while((pmalloc(size))0)if(_callnewh(size)0){// report no memory// 如果申请内存失败了这里会抛出bad_alloc 类型异常staticconststd::bad_alloc nomem;_RAISE(nomem);}return(p);}// operator delete:// 该函数最终是通过free来释放空间的voidoperatordelete(void*pUserData){_CrtMemBlockHeader*pHead;RTCCALLBACK(_RTC_Free_hook,(pUserData,0));if(pUserDataNULL)return;_mlock(_HEAP_LOCK);// block other threads__TRY// get a pointer to memory block headerpHeadpHdr(pUserData);// verify block type_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead-nBlockUse));_free_dbg(pUserData,pHead-nBlockUse);//此处调用free函数__FINALLY_munlock(_HEAP_LOCK);// release other threads__END_TRY_FINALLYreturn;}// free的实现#definefree(p)_free_dbg(p,_NORMAL_BLOCK)通过上述的实验我们知道operator new实际也是通过malloc来申请空间如果malloc申请空间成功就直接返回否则执行用户提供的空间不足应对措施如果用户提供了该措施就继续申请否则就抛异常。operator delete最终是通过free来释放空间的。4.3 汇编区别【new底层汇编】从图中可以看到是先调用operator new再调用相对应的构造函数【delete底层汇编】4.4 深入 deletedelete的底层是先调用析构函数然后再调用operator delete为什么会这样呢我们先了解一下下面这个【模拟栈对象如何在堆上申请/释放空间】先为栈对象开空间然后调用该对象的构造函数为_array开辟空间最终让栈上的 p1 指针指向堆上的对象栈对象和栈内存区域是两个不同领域的概念释放空间的话要先调用析构函数如果你先调用operator delete了_array就成野指针了就会造成内存泄露五、定位 new 表达式 (了解)5.1 定位 new 的用法定位new是一种 “在已分配的原始内存中调用构造函数” 的特殊用法主要配合内存池使用内存池分配的内存未初始化需手动调用构造函数new(place_address)type 或者new(place_address)type(initializer-list)其中place_address必须是一个指针initializer-list是类型的初始化列表如下classA{public:A(inta0):_a(a){coutA():thisendl;}~A(){cout~A():thisendl;}private:int_a;};intmain(){// p1现在指向的只不过是与A对象相同大小的一段空间还不能算是一个对象因为构造函数没有执行A*pa(A*)malloc(sizeof(A));//定位new--对pa指向的空间显式调用构造函数new(pa)A(1);pa-~A();//析构函数可以直接显式调用或者直接使用deletefree(pa);}5.2 使用场景定位new表达式在实际中一般是配合内存池使用 —— 因为内存池分配出的内存没有初始化所以如果是自定义类型的对象需要使用new的定义表达式进行显式调用构造函数进行初始化而内存池在后面我们会详细学习此处我们了解一下即可。简单理解一下内存池假设半山腰有一个村子但由于各种原因村子中没有水喝所以人们每次喝水都只能到山下的公共水井处排队打水但是呢排队很慢所以村长就用抽水机水管联通水井在自己家建了一个蓄水池以后要用水就直接到蓄水池中去取即可而不用再到山下去排队打水了大大提高了效率。上述例子中全村公用的水井就相当于堆其他村民排队打水就相当于malloc/calloc/realloc函数向堆区申请空间而村长家的蓄水池就相当于我们的主角 —— 内存池内存池的建立可以使得我们申请空间的效率变得很高。六、总结问题malloc/free和new/delete的区别malloc/freenew/delete本质函数操作符内置类型初始化不会初始化可初始化如new int(5)自定义类型只开辟空间不调用构造/析构先开辟空间再调用构造函数释放前先调用析构函数大小指定手动计算字节数并传入自动推导类型大小多个对象用new T[N]返回值void*必须强转返回对应类型指针无需强转失败处理返回NULL需手动判空抛出std::bad_alloc需捕获异常什么是内存泄漏内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况内存泄漏并不是指内存在物理上的消失而是应用程序分配某段内存后因为设计错误失去了对该段内存的控制因而造成了内存的浪费。内存泄漏的危害短期运行的程序发生内存泄漏危害不大因为当程序结束时动态申请的空间全部都会被回收长期运行的程序出现内存泄漏影响很大如操作系统、后台服务等等出现内存泄漏会导致响应越来越慢最终卡死。如何避免内存泄漏?工程前期良好的设计规范养成良好的编码规范申请的内存空间记着匹配的去释放。ps这个理想状态。但是如果碰上异常时就算注意释放了还是可能会出问题。需要下一条智能指针来管理才有保证。采用 RAII 思想或者智能指针来管理资源。有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。出问题了使用内存泄漏工具检测。ps不过很多工具都不够靠谱或者收费昂贵。
RELATED

相关推荐

【C++】 初阶篇 类与对象(中) 六大默认成员函数

【C++】 初阶篇 类与对象(中) 六大默认成员函数

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/21 20:34:36
docker部署C# .net程序

docker部署C# .net程序

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/21 20:34:36
【RHCA+】_etc_yum.conf

【RHCA+】_etc_yum.conf

“ 目录:RHCA -> 6 软件仓库 -> 33 _etc_yum.conf ”yum默认配置文件[rootwuzuniao ~]# ll /etc/yum.conf #查看配置文件权限-rw-r--r--. 1 root root 813 Apr 15 2014 /etc/yum.conf[rootwuzuniao ~]# cat /etc/yum.conf #查看配置文件默认内容[main] #主区域…

📅 2026/10/21 20:29:35
MORE NEWS

更多资讯

📰

Docker+WordPress阿里云部署实战:从零搭建高可用个人博客

1. 为什么现在还要亲手搭一个 WordPress 博客?不是有现成的平台吗? “dockerwordpress搭建个人博客(如何在阿里云服务器上线部署个人博客)”——这个标题背后,藏着一群正在悄悄回归技术掌控权的人。不是他们不信公众号…

📰

对比预测编码CPC:自监督学习中的对比学习与InfoNCE损失解析

1. 从"预测未来"说起:对比预测编码到底在做什么第一次看到"Representation Learning with Contrastive Predictive Coding"这个标题,很多人会以为它只是又一堆堆砌术语的论文。但如果你真正动手复现过,就会发现它解决的是…

📰

PHP调用人脸融合接口:完整链路Demo与避坑指南

简介:这是一份基于腾讯AI人脸融合API的PHP示例项目,主要面向希望快速集成人脸融合能力的Web开发者,可用于制作换装、合影、趣味营销等H5活动。项目参考了此前微信中流行的换军装玩法,通过调用腾讯AI平台接口完成人脸检测与融合处理…

📰

车牌识别可信度建模:三层过滤策略实战指南

1. 项目概述:为什么车牌识别这件事,值得拆到第七篇还在深挖? HyperLPR 这个名字,对做过图像识别、智能交通或安防类项目的开发者来说,几乎等于“开箱即用的车牌识别第一选择”。它不是那种需要从OpenCV读图、YOLO训练、…

📰

基于YOLOv8的钢材表面缺陷检测实战:数据转换、训练调优与工业部署

1. 为什么选择YOLOv8做钢材瑕疵检测:从产线痛点说起 钢铁表面缺陷检测这件事,说出来你可能不太信,很多产线到今天还在靠人工肉眼盯屏幕。钢带在产线上以每秒好几米的速度跑,质检员盯着监视器里的带钢表面,连续盯几个小…

📰

发那科FANUC电路板图纸:从电源到IGBT的数控维修核心解析

简介:发那科电路板图纸全套,面向数控设备维修人员、自动化工程师及电路板研究爱好者,涵盖主控制板、驱动板与电源模块的关键线路图,可用于日常检修、故障定位、系统升级与备件对照。压缩包共5个文件,大小约1.1兆&#…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬