目录1. C/C内存分布2. C语言中的动态内存管理3. C中的内存管理方式3.1自定义类型的使用3.2new[] 会在内存块头部额外分配空间存储数组大小以便 delete[] 知道要调用多少次析构函数情况1内置类型数组情况2自定义类型数组需要析构详细的内存分配过程验证实验实验1观察内存布局实验2如果使用错误的 delete4. operator new 与 operator deleteoperator new 的实现原理operator delete 的实现5. 定位new表达式placement new总结在C/C编程中内存管理是一个绕不开的重要话题。正确地管理内存不仅能提高程序的运行效率还能避免内存泄漏、崩溃等问题。本文将从内存分布、C语言中的动态内存管理、C中的new/delete、底层函数operator new/delete以及它们与malloc/free的区别等方面带你系统地理解C/C内存管理的核心知识。1. C/C内存分布在C/C中程序的内存通常分为以下几个区域栈Stack存放非静态局部变量、函数参数、返回值等。栈是向下增长的内存由编译器自动管理。堆Heap用于程序运行时动态内存分配堆是向上增长的需要手动申请和释放。数据段静态区存放全局变量和静态变量。代码段常量区存放可执行代码和只读常量。int globalVar 1; static int staticGlobalVar 1; void Test() { static int staticVar 1; int localVar 1; int num1[10] {1,2,3,4}; char char2[] abcd; const char* pChar3 abcd; 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); }globalVar在哪里 ——数据段staticGlobalVar在哪里 ——数据段staticVar在哪里 ——数据段localVar在哪里 ——栈num1在哪里 ——栈char2在哪里 ——栈*char2在哪里 ——栈pChar3在哪里 ——栈*pChar3在哪里 ——代码段ptr1在哪里 ——栈*ptr1在哪里 ——堆注意*char2是数组内容存储在栈中而*pChar3是指向字符串常量的指针字符串常量存储在代码段中。2. C语言中的动态内存管理C语言提供了四个函数用于动态内存管理malloc申请指定大小的内存不初始化。calloc申请并初始化为0。realloc调整已申请内存的大小。free释放内存。int* p2 (int*)calloc(4, sizeof(int)); int* p3 (int*)realloc(p2, sizeof(int)*10); // 不需要单独free(p2)因为realloc会处理原内存的释放 free(p3);malloc/calloc/realloc 的区别malloc只分配内存不初始化。calloc分配内存并初始化为0。realloc调整已分配内存的大小可能移动数据。malloc 的实现原理在glibc中malloc通过brk或mmap系统调用从操作系统申请内存并维护一个空闲链表来管理内存块。3. C中的内存管理方式C保留了C的内存管理方式但引入了new和delete操作符它们不仅能分配内存还能调用构造函数和析构函数。int* p4 new int; // 申请一个int空间 int* p5 new int(10); // 申请并初始化为10 int* p6 new int[3]; // 申请3个int空间 delete p4; delete p5; delete[] p6; // 匹配使用new 单个变量 → 用 deletenew 数组 [] → 必须用 delete []不匹配会导致内存泄漏、程序崩溃3.1自定义类型的使用class A { public: A(int a 0) : _a(a) { cout A() endl; } ~A() { cout ~A() endl; } private: int _a; }; int main() { A* p1 (A*)malloc(sizeof(A)); // 只向堆申请一块大小为 A 的内存不会调用构造函数 // 输出什么都没有 A* p2 new A(); // 分配内存 调用构造 free(p1); // 只释放内存不调用析构 delete p2; // 调用析构 释放内存 A* p3 new A[10]; // 申请10块空间 调用10次构造函数 delete[] p3; // 调用10次析构 释放内存 }3.2new[] 会在内存块头部额外分配空间存储数组大小以便 delete[] 知道要调用多少次析构函数问题场景class A { public: A() { cout 构造函数 endl; } ~A() { cout 析构函数 endl; } private: int data; }; int main() { A* p new A[10]; // 创建10个对象 delete[] p; // 需要知道要调用10次析构函数 }关键问题当执行delete[] p时程序怎么知道p指向的是单个对象还是数组如果是数组又怎么知道有多少个元素情况1内置类型数组int* p new int[10]; delete[] p;对于内置类型不需要调用析构函数所以内存布局很简单┌─────────────────────────────────────────────────────┐│ int[0] │ int[1] │ int[2] │ ... │ int[9] │└─────────────────────────────────────────────────────┘↑p 指向这里第一个元素的地址情况2自定义类型数组需要析构A* p new A[10]; delete[] p;编译器会在内存块头部额外分配空间存储元素个数实际分配的内存块起始地址↓┌───────────────────────────────────────────────────────────│ 数组大小 │ A[0] │ A[1] │ A[2] │ ... │ A[9] ││ (10) │ │ │ │ │ │└───────────────────────────────────────────────────────────↑p 指向这里仍然指向第一个元素重要p指向的是第一个元素而不是数组大小的存储位置但第一个元素之前还有额外的空间存储数组大小所以 p 不是整个内存块的起始地址详细的内存分配过程new A[10] 的实际操作A* p new A[10];编译器实际生成的代码类似// 1. 计算需要的内存大小 size_t element_size sizeof(A); // 假设是 4 字节 size_t array_size 10 * element_size; // 40 字节 size_t total_size array_size sizeof(size_t); // 40 8 48 字节 // 2. 分配内存多分配了存储数组大小的空间 void* raw_mem operator new(total_size); // 分配 48 字节 // 3. 在头部存储数组大小 *(size_t*)raw_mem 10; // 存储元素个数 // 4. 跳过头部返回第一个元素的位置 A* p (A*)((char*)raw_mem sizeof(size_t)); // 5. 调用构造函数初始化每个元素 for (size_t i 0; i 10; i) { new (p i) A(); // placement new调用构造函数 }关键点new会自动调用构造函数delete会自动调用析构函数而malloc和free不会。delete[] p 的实际操作delete[] p;编译器实际生成的代码类似// 1. 获取数组的起始地址p指向第一个元素 void* raw_mem (char*)p - sizeof(size_t); // 回退到头部 // 2. 读取数组大小 size_t count *(size_t*)raw_mem; // 读取存储的10 // 3. 调用每个元素的析构函数 for (size_t i 0; i count; i) { p[i].~A(); // 调用析构函数 } // 4. 释放完整的内存块 operator delete(raw_mem);验证实验实验1观察内存布局#include iostream using namespace std; class A { public: A() : data(0) { cout A() 构造 at this endl; } ~A() { cout ~A() 析构 at this endl; } private: int data; }; int main() { A* p new A[3]; cout \n 内存地址分析 endl; cout p 指向的地址: p endl; cout p 的前一个地址: ((char*)p - sizeof(size_t)) endl; cout sizeof(A) sizeof(A) endl; cout sizeof(size_t) sizeof(size_t) endl; // 尝试读取头部存储的大小 size_t* header (size_t*)((char*)p - sizeof(size_t)); cout \n头部存储的数组大小: *header endl; delete[] p; return 0; }实验2如果使用错误的 deleteA* p new A[10]; delete p; // 错误应该用 delete[]这会导致只调用第一个元素的析构函数释放错误的内存地址p 而不是 raw_mem导致内存泄漏和程序崩溃总结malloc / free是 C 语言的函数只管开空间、放空间不管对象死活只开空间不调用构造函数只释放空间不调用析构函数new / delete是 C 的运算符专门为对象服务开空间 调用构造函数调用析构函数 释放空间4. operator new 与 operator deletenew和delete是操作符底层通过operator new和operator delete全局函数实现。operator new 的实现原理void* __CRTDECL operator new(size_t size) { void* p; while ((p malloc(size)) 0) { if (_callnewh(size) 0) { // 抛出 bad_alloc 异常 } } return p; }operator new内部调用malloc分配内存。如果分配失败会调用用户设置的“内存不足处理函数”若未设置则抛出异常。operator delete 的实现void operator delete(void* p) { free(p); }operator delete内部调用free释放内存。new/delete 与 malloc/free 的区别特性malloc/freenew/delete本质函数操作符初始化不初始化可初始化大小计算手动计算自动计算返回值void*需强转类型安全失败处理返回NULL抛出异常构造/析构不调用调用总结newoperator new分配内存 构造函数delete 析构函数 operator delete释放内存operator new底层调用malloc但增加了错误处理机制失败时会调用 new handler 并循环重试最后抛出异常5. 定位new表达式placement new定位new允许在已分配的内存上构造对象常用于内存池中。A* p1 (A*)malloc(sizeof(A)); // 分配内存 new(p1) A(); // 在p1位置构造对象 p1-~A(); // 手动调用析构 free(p1); // 释放内存注意定位new不分配内存只调用构造函数必须显式调用析构函数。总结C/C程序的内存分为栈、堆、数据段、代码段等区域。C语言使用malloc/calloc/realloc/free进行动态内存管理。C引入new/delete支持初始化、构造/析构调用。operator new/delete是底层实现函数new/delete是操作符。定位new允许在已有内存上构造对象适合内存池场景。使用new[]时必须使用delete[]否则行为未定义。