C++ 内存管理

C++ 内存管理 内存管理1. C/C内存分布进程地址空间各区域功能说明1. 代码段Code Segment / 文本段 / 常量区2. 数据段Data Segment / 静态区3. 堆Heap4. 代码区Code Segment5. 栈Stack / 堆栈示例代码2. C语言中动态内存管理方式malloc/calloc/realloc/free示例代码1. malloc/calloc/realloc 的区别2. malloc 的实现原理3. C内存管理方式3.1 new/delete操作内置类型核心用法对比核心知识点3.2 new和delete操作自定义类型核心知识点4. operator new 与 operator delete 函数4.1 operator new 与 operator delete 函数重点核心概念核心代码实现核心知识点5. new和delete的实现原理5.1 内置类型核心知识点5.2 自定义类型new的实现原理delete的实现原理new T[N]的实现原理delete[]的实现原理核心代码实现与原理模拟关键补充知识点7. 内存泄漏7.1 什么是内存泄漏 内存泄漏的危害1. C/C内存分布进程地址空间各区域功能说明进程地址空间是操作系统为每个进程划分的虚拟内存地址范围并非物理内存的直接映射。不同区域有严格的功能划分和访问规则从低地址到高地址依次为1. 代码段Code Segment / 文本段 / 常量区核心功能存储程序中可执行的机器指令编译后的二进制代码、只读常量如字符串常量、const修饰的只读全局常量等。访问权限只读READ-ONLY禁止写入操作防止程序指令被篡改提升安全性。存储内容示例函数的二进制执行代码如Test()函数的指令。字符串常量如代码char* c abcd中的abcd。const修饰的全局只读常量。特性多个进程可共享同一份代码段如系统库函数的代码节省内存。程序运行期间大小固定不会动态变化。2. 数据段Data Segment / 静态区核心功能存储全局数据、静态数据分为初始化数据段和未初始化数据段。初始化数据段存储已初始化的全局变量、静态变量如globalVar、staticGlobalVar、staticVar。未初始化数据段存储未初始化的全局变量、静态变量程序启动时操作系统会自动将其初始化为0。访问权限可读可写READ-WRITE运行期间可修改变量值。特性生命周期与进程一致程序启动时分配内存程序退出时释放。大小在编译期确定运行期间不会动态扩展/收缩。全局变量和静态变量默认存储在此区域无需手动分配/释放。3. 堆Heap核心功能程序运行时动态内存分配的核心区域用于存储运行期按需创建的数据。访问权限可读可写READ-WRITE。特性地址空间向上增长从低地址向高地址扩展如上面的图片中的样子。生命周期由程序员控制需手动通过malloc/calloc/realloc申请free释放若未释放会导致内存泄漏。大小不固定可随动态内存分配/释放灵活变化。堆的管理由操作系统的内存管理器完成频繁申请/释放会产生内存碎片。4. 代码区Code Segment核心功能存储程序编译后的可执行机器指令如函数的二进制执行代码是CPU能直接解析运行的核心内容。存储程序中的只读常量如字符串常量、const修饰的全局只读常量保障常量不被篡改。作为程序运行的基础载体所有进程的执行逻辑均依赖代码区的指令序列。访问权限默认只读READ-ONLY禁止写入操作防止程序指令或只读常量被恶意/误操作修改提升程序运行安全性。特性是进程地址空间的基础核心区域程序启动时优先加载退出时最后释放。多个进程可共享同一份代码区内容如系统标准库函数的指令大幅节省内存资源。大小在编译期确定程序运行期间固定不变无动态扩展/收缩可能。5. 栈Stack / 堆栈核心功能存储非静态局部变量、函数参数、函数返回值、函数调用的上下文信息如返回地址、ebp寄存器值等。访问权限可读可写READ-WRITE。特性地址空间向下增长从高地址向低地址扩展如图所示。由操作系统自动管理函数调用时分配栈帧函数返回时释放栈帧无需程序员干预。栈的大小固定通常为几MB超出会触发栈溢出Stack Overflow会直接结束程序。存储的变量生命周期为函数调用周期函数执行结束后变量立即销毁。栈帧是栈的基本单位每个函数调用对应一个独立的栈帧保护函数调用的独立性。示例代码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. 代码段 (常量区)那么代码中的不同变量所在的分区为表中所示变量位置详细解析globalVarCglobalVar 是全局初始化变量存储在数据段.data 区。staticGlobalVarCstaticGlobalVar 是全局静态初始化变量存储在数据段.data 区。staticVarCstaticVar 是局部静态初始化变量存储在数据段.data 区。localVarAlocalVar 是局部非静态变量存储在栈区Test 函数的栈帧中。num1Anum1 是局部非静态数组存储在栈区数组元素均在栈帧内。char2Achar2 是局部非静态字符数组存储在栈区。*char2A*char2 是 char2 数组首元素的解引用数组本身在栈区因此指向栈区的数组元素‘a’。pChar3ApChar3 是局部非静态指针变量存储在栈区。*pChar3D*pChar3 指向字符串常量 “abcd”该常量存储在代码段只读常量区。ptr1Aptr1 是局部非静态指针变量存储在栈区。*ptr1B*ptr1 是 ptr1 指向的动态内存由 malloc 分配存储在堆区。衍生问题问题答案详细解析sizeof(num1)40num1 是 int 类型数组包含 10 个元素int 占 4 字节10*440sizeof 计算数组总字节数与数组元素初始化与否无关。sizeof(char2)5char2 是字符数组存储 “abcd” 时编译器自动追加 ‘\0’ 作为字符串结束符共 5 个 char 元素char 占 1 字节总字节数为 5。strlen(char2)4strlen 是库函数仅统计 ‘\0’ 之前的有效字符数“abcd” 共 4 个字符因此结果为 4。sizeof(pChar3)4/8pChar3 是指针变量32 位系统下指针占 4 字节64 位系统下指针占 8 字节sizeof 计算指针本身的字节数与指向的内容无关。strlen(pChar3)4pChar3 指向代码段的字符串常量 “abcd”strlen 统计 ‘\0’ 前字符数结果为 4。sizeof(ptr1)4/8ptr1 是指针变量32 位系统占 4 字节64 位系统占 8 字节所有指针类型的 sizeof 结果仅与系统位数相关。sizeof 和 strlen 核心区别说明本质属性sizeofC/C 的内置运算符编译期间完成计算不涉及运行时逻辑。strlenC 标准库函数string.h运行期间遍历字符串直到遇到 ‘\0’ 停止计算。计算范围sizeof计算变量 / 数据类型占用的总字节数包含 ‘\0’、数组未初始化部分等所有内存。strlen仅统计字符串中 ‘\0’ 之前的有效字符个数不包含 ‘\0’也不处理非字符串数据。适用场景sizeof可用于任意数据类型int、char、数组、指针、结构体、类等无格式要求。strlen仅适用于以 ‘\0’ 结尾的字符串char*对非字符串类型如 int 数组使用会导致未定义行为越界访问。参数要求sizeof参数可以是类型名如 sizeof (int)、变量名如 sizeof (num1)、表达式如 sizeof (12)。strlen参数必须是指向以 ‘\0’ 结尾的字符串的指针char*不能直接传入类型名。返回值意义sizeof返回值表示内存占用的字节数反映数据的存储大小。strlen返回值表示字符串的有效长度反映字符串的逻辑长度。### 1. C/C内存分布2. C语言中动态内存管理方式malloc/calloc/realloc/free示例代码voidTest(){// malloc申请内存 未初始化int*p1(int*)malloc(sizeof(int));free(p1);// 释放malloc申请的内存// calloc申请内存 自动初始化int*p2(int*)calloc(4,sizeof(int));// realloc调整p2指向的内存大小int*p3(int*)realloc(p2,sizeof(int)*10);// 无需free(p2) realloc成功后p2已失效free(p3);// 释放realloc调整后的内存}1. malloc/calloc/realloc 的区别核心区别总结malloc功能向堆区申请指定字节数的连续内存空间。初始化申请的内存未初始化内容为随机值。参数仅需传入申请的字节数例如malloc(sizeof(int)*4)。calloc功能向堆区申请指定数量 指定大小的连续内存空间。初始化申请的内存自动初始化为 0。参数需传入元素个数和单个元素大小例如calloc(4, sizeof(int))。等价于malloc(4*sizeof(int))memset(申请的内存, 0, 4*sizeof(int))。realloc功能调整已申请动态内存的大小扩大或缩小。内存处理若原内存后有足够连续空间直接扩容返回原地址。若原内存后无足够空间重新申请新内存拷贝原数据释放原内存返回新地址。参数需传入原内存指针和新的总字节数例如realloc(p2, sizeof(int)*10)。注意成功调用后原指针失效无需释放原指针仅释放新返回的指针。关键注意点realloc调整内存后原指针如示例中的p2无需调用free否则会导致重复释放。三者申请的内存均在堆区需手动调用free释放否则会造成内存泄漏。2. malloc 的实现原理核心原理(简要说明)底层依赖基于操作系统的内存管理接口如 Linux 的brk/sbrk/mmap实现。内存池机制malloc并非每次申请都直接向操作系统要内存而是先向系统申请一块大内存作为内存池。后续小内存申请直接从内存池分配减少系统调用开销。空闲链表管理用链表管理已释放的空闲内存块记录块的大小和地址。申请内存时遍历空闲链表按适配策略如首次适配或最佳适配找到合适的内存块分配。内存对齐分配的内存会按系统要求对齐如 8 字节或 16 字节对齐保证内存访问效率。扩容机制若内存池不足会再次向操作系统申请新的大块内存补充内存池。3. C内存管理方式C语言内存管理方式在C中可继续使用但在自定义类型场景下存在局限且使用繁琐因此C提出专属内存管理方式通过new和delete操作符实现动态内存管理。3.1 new/delete操作内置类型核心用法对比实现方式代码示例核心特点C语言malloc/freeint* p1 (int*)malloc(sizeof(int));int* p3 (int*)malloc(sizeof(int)*10);free(p1); free(p3);malloc是函数需手动强转类型仅完成内存申请/释放Cnew/deleteint* p2 new int;int* p2_init new int(10);int* p4 new int[10];delete p2; delete[] p4;new是操作符无需强转类型支持单个元素初始化核心知识点申请释放单个元素空间必须匹配使用new和delete。申请释放连续空间必须匹配使用new[]和delete[]。内置类型数组无法通过new int[10](10)方式统一初始化。内置类型场景下new/delete仅简化语法功能等价于malloc/free。3.2 new和delete操作自定义类型classA{public:A(){_a0;coutA()endl;}~A(){cout~A()endl;}private:int_a;};voidTest2(){A*p1(A*)malloc(sizeof(A));A*p4newA;free(p1);deletep4;}核心知识点内置类型场景new/delete与malloc/free功能效果一致。自定义类型场景核心差异malloc仅申请内存空间不调用构造函数。free仅释放内存空间不调用析构函数。new在申请空间后自动调用构造函数完成对象初始化。delete在释放空间前自动调用析构函数完成对象资源清理。C中优先使用new/delete管理自定义类型内存。4. operator new 与 operator delete 函数4.1 operator new 与 operator delete 函数重点核心概念new/delete是用户层的内存申请/释放操作符。operator new/operator delete是系统提供的全局函数并非运算符重载。new底层调用operator new申请空间delete底层调用operator delete释放空间。operator new用法与malloc一致。核心代码实现voidTest3(){size_t size2;void*p3malloc(size*1024*1024*1024);coutp3endl;free(p3);try{void*p4operatornew(size*1024*1024*1024);coutp4endl;operatordelete(p4);}catch(exceptione){coute.what()endl;}}核心知识点内存大小计算需使用无符号整形size_t有符号整形范围为− 2 31 ∼ 2 31 − 1 -2^{31} \sim 2^{31}-1−231∼231−12 × 1024 × 1024 × 1024 2 31 2 \times 1024 \times 1024 \times 1024 2^{31}2×1024×1024×1024231超出范围会导致溢出。malloc与operator new的核心区别malloc申请空间失败返回NULL。operator new申请空间失败抛出异常异常是面向对象处理错误的标准方式。operator new底层实现逻辑调用malloc申请内存空间申请成功直接返回申请失败则执行用户提供的空间不足应对措施无应对措施则抛异常。operator delete底层实现逻辑最终通过free释放空间释放失败直接终止进程。设计operator delete仅为与operator new成对出现功能与free无区别。new的完整逻辑operator new申请空间 调用构造函数初始化。delete的完整逻辑调用析构函数清理资源 operator delete释放空间。new相比malloc的核心优势自动调用构造函数初始化、申请失败抛异常。delete相比free的核心优势自动调用析构函数清理资源。5. new和delete的实现原理5.1 内置类型核心知识点内置类型场景下new/malloc、delete/free 功能基本一致。核心差异点单个元素空间使用 new/delete连续空间使用 new[]/delete[]需严格匹配。new 申请空间失败时抛出异常malloc 申请失败返回 NULL。5.2 自定义类型new的实现原理调用 operator new 函数完成内存空间申请。在申请到的内存空间上执行构造函数完成对象初始化。delete的实现原理在目标内存空间上执行析构函数完成对象内资源的清理。调用 operator delete 函数释放该内存空间。new T[N]的实现原理调用 operator new[] 函数该函数内部实际调用 operator new 函数完成 N 个对象所需连续内存空间的申请。在申请到的连续空间上依次执行 N 次构造函数完成 N 个对象的初始化。delete[]的实现原理在待释放的连续内存空间上依次执行 N 次析构函数完成 N 个对象的资源清理。调用 operator delete[] 函数释放空间该函数内部实际调用 operator delete 函数完成内存释放。核心代码实现与原理模拟classB{public:B(intb0):_b(b){coutB()endl;}~B(){cout~B()endl;}private:int_b;};voidTest4(){// 标准 new/delete 使用方式B*p1newB;deletep1;// 手动模拟 new 的实现过程// 步骤 1调用 operator new 申请内存空间B*p2(B*)operatornew(sizeof(B));// 步骤 2使用定位 new 在已申请空间上执行构造函数初始化// 语法new(内存空间指针)类名(构造函数参数)new(p2)B(1);// 手动模拟 delete 的实现过程// 步骤 1显式调用析构函数清理资源析构函数公有可显式调用构造函数不可p2-~B();// 步骤 2调用 operator delete 释放内存空间operatordelete(p2);}关键补充知识点定位 newplacement new用于在已申请的内存空间上显式调用构造函数完成对象初始化语法为 new(空间指针)类型(构造参数)。构造函数不可显式调用仅能由编译器自动调用或通过定位 new 间接调用。析构函数为公有属性时可显式调用用于手动触发资源清理。堆区申请 4G 大小空间的前提编译器需调整为 64 位32 位系统地址空间不足无法支持。7. 内存泄漏7.1 什么是内存泄漏 内存泄漏的危害内存泄漏的定义内存泄漏是指因程序设计疏忽或错误导致已分配且不再使用的内存未能被释放的情况。内存泄漏并非物理内存消失而是程序失去对该段内存的控制权造成内存资源的浪费。内存泄漏的危害长期运行的程序受内存泄漏影响极大如操作系统、后台服务、服务器程序等。泄漏的内存会持续占用系统资源导致可用内存逐渐减少程序响应速度越来越慢。严重时会耗尽系统所有可用内存造成程序卡死、系统崩溃。典型场景示例服务器后台服务电商平台的订单处理服务每处理一个订单就泄漏 1KB 内存单日百万级订单量会导致泄漏约 1GB 内存运行一周后内存耗尽服务无响应订单无法处理。操作系统组件系统桌面进程存在内存泄漏每打开/关闭一个文件就泄漏少量内存长期使用后系统可用内存不足桌面卡顿、操作延迟最终需重启系统。嵌入式设备程序智能家居的控制芯片程序因内存泄漏持续占用有限的嵌入式内存运行数月后内存耗尽设备失去响应无法执行开关灯、调节温度等指令。