博主名称鱼子星_✅数据结构专栏【数据结构】✅算法竞赛专栏【算法竞赛】✅C系列专栏【C从零开始系列】前言在 C/C 编程中动态内存管理是构建高效、灵活程序的核心技能之一。从 C 语言的malloc/free到 C 的new/delete看似简单的内存申请与释放背后隐藏着语言设计哲学、对象生命周期管理以及底层内存机制的深刻差异。本文旨在系统性地剖析 C 动态内存管理的全貌。我们将从内存分布的基础知识出发逐步对比 C 与 C 的动态内存开辟方式深入探讨new/delete对内置类型与自定义类型的处理机制、其底层实现原理operator new/operator delete并拓展至定位new表达式等高级话题。通过清晰的代码示例、内存布局图解和面试题解析帮助你不仅掌握语法更理解其设计意图与适用场景从而在项目中做出更优雅、更安全的内存管理决策。无论你是正在巩固基础的初学者还是希望深入理解 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.代码段(常量区) 1.globalVar在哪里__C__ 2.staticGlobalVar在哪里__C__ 3.staticVar在哪里__C__ 4.localVar在哪里__A__ 5.num1 在哪里__A__ 6.char2在哪里__A__ 7.*char2在哪里__A__ 8.pChar3在哪里__A__ 9.*pChar3在哪里__D__ 10.ptr1在哪里__A__ 11.*ptr1在哪里__B__题目解析globalVar是全局变量生命周期持续到程序结束所以内存在静态区staticGlobalVar本身就是全局变量再加上static也还是在静态区staticVar被static修饰内存在静态区localVar局部变量的内存都在栈上num1也属于局部变量char2是局部字符数组的变量名内存在栈上*char2看似是静态的字符串abcd但是它的内存还是在栈上有如下 2 种理解方式*char2是常量区中的abcd的拷贝abcd是局部域的数组的元素当函数结束时函数内部的数据会随着栈帧销毁所以*char2在栈上pChar3也在栈上因为 const 修饰的并不是pChar3而是*pChar3*pChar3被 const 修饰在常量区ptr1知识局部域的一个指针变量在栈上*ptr1是动态申请的内存内存在堆上测试验证内存空间在C中有如下几种内存空间存储着不同属性的数据栈堆栈存储着非静态局部变量/函数参数/返回值。栈的空间是自顶向下延伸的堆动态申请的空间都在堆上数据段静态区存储全局数据和静态数据代码段常量区存储只读常量二. C语言中动态内存开辟的方法动态内存开辟malloc/calloc/realloc//1.malloc/calloc/realloc的区别是什么int*p1(int*)malloc(sizeof(int)*10);int*p2(int*)realloc(p1,sizeof(int)*20);free(p2);面试题分析malloc/calloc/realloc和区别是什么malloc只能用于动态内存开辟对开辟的空间不能初始化calloc可以对动态开辟的空间初始化realloc用于对已有的空间扩容扩容有两种情况原地扩容和拷贝数据到另外的地方扩容malloc的实现原理是什么感兴趣的可以点击这个链接查看【CTF】GLibc堆利用入门-机制介绍三. C中动态内存开辟的方法1. new/delete的语法使用C中一般使用关键字new和delete进行动态内存开辟对于单个元素的内存开辟其使用的格式为类型*变量名new类型(初始化的值);//初始化的值可以写也可以不写delete变量名;如果要申请一块连续的动态内存开辟时在类型之后使用[size]来定义开辟的大小size为需要申请的元素个数释放内存时在delete后加上[]定义格式为类型*变量名new类型[size];//初始化的方式和数组相同delete[]变量名;️ 使用关键字 new 动态开辟内存时返回值不需要使用类型转换new会自行匹配返回值的类型2. new/delete动态申请内置类型对于内置类型的动态开辟new/delete和malloc/free的功能并没有很大的差别除了使用更加方便外new主要的优势就是动态开辟内存时可以顺便初始化。//使用new动态开辟内存int*p1newint(10);//直接将*p1初始化成了10int*arr1newint[10]{1,2,3};deletep1;delete[]arr1;//使用malloc动态开辟内存int*p2(int*)malloc(sizeof(int));*p210;int*arr2(int*)malloc(sizeof(int)*10);free(p2);free(arr2);使用new直接初始化使用malloc不能直接初始化3. new/delete动态申请自定义类型使用new和delete对自定义类型进行动态内存开辟对比使用malloc和free有一个绝对的优势就是使用new进行动态内存开辟会自动调用自定义类型的默认构造函数使用delete释放内存会自动调用自定义类型的析构函数#includeiostreamusingnamespacestd;classA{public:A(inta1):_a(a){coutA(int a 1)endl;}~A(){_a0;cout~A()endl;}private:int_a;};intmain(){A*pnewA;deletep;return0;}使用上代码可以进行验证输出结果为如果使用new和delete进行多个元素的动态内存申请和销毁编译器会相应的调用多次构造函数和析构函数为什么这里又说new会调用构造函数呢其实上面已经说了使用new进行动态内存开辟可以直接初始化这个方法也适用于自定义类型。那要如何进行初始化呢这就需要看自定义类型中写的构造函数有多少种样子了只要有相应的构造函数那么用什么样的初始化方式都没问题这里针对有意义的初始化。所以这里其实也可以得出使用new对自定义类型进行动态内存开辟的初始化是根据初始化的形式调用构造函数的如果没有显式的初始化就会调用默认构造函数反之会调用参数最适合的构造函数。️ 如果是对多个元素每个元素都有多参数的初始化的动态内存开辟每组参数需要使用{}括起来A*pnewA[3]{{1,2},{3,4},{4,5}};classA{public:A(inta1):_a(a){coutA(int a 1)endl;}A(inta1,inta2):_a(a1a2){coutA(int a1, int a2)endl;}~A(){_a0;cout~A()endl;}private:int_a;};intmain(){A*p1newA(10);A*p2newA(10,10);deletep1;deletep2;return0;}️ 输出-小结使用malloc动态内存分配不能对申请的空间进行初始化使用new可以使用new/delete在内存申请时会自动调用构造函数在内存释放时会调用析构函数new对自定义类型的空间初始化会根据初始化的方式选择合适的构造函数例如没有显式传参就调用默认构造如果有就会对照初始化的参数选择最合适的构造函数️ 如果自定义类型没有默认构造则使用new一定要显式的初始化4. new/delete的使用场景我们使用malloc/free对链表类进行动态内存分配时初始化需要手动写一个Init函数进行初始化而对于链表自身申请的空间还需要手动的调用析构函数来释放内存这样操作很不方便classList{public:List(intn4){_arr(int*)malloc(sizeof(int)*n);_capacity4;_size0;}~List(){if(_arr){free(_arr);_arrnullptr;}_capacity_size0;}voidInit(intn4){_arr(int*)malloc(sizeof(int)*n);_capacity4;_size0;}private:int*_arr;int_capacity;int_size;};intmain(){//无法初始化List*lt(List*)malloc(sizeof(List));(*lt).Init();//手动调用析构函数(*lt).~List();free(lt);return0;}而如果使用new/delete进行动态内存申请它们会自动的调用 List 的构造函数和析构函数这也就代表使用 new 不仅会申请lt指针的内存更会调用 List 的构造函数将链表初始时的内存也申请了使用 delete 释放空间不仅仅是释放lt指针的内存更会先调用 List 的析构函数释放链表动态申请的空间可谓事半功倍//自动调用构造函数List*ltnewList;//自动调用析构函数deletelt;️ 这里还有一点需要注意new 是先申请空间再调用构造函数delete 是先调用析构函数再释放指针的空间5. 【拓展】new和delete的配套使用new和delete一定要配套使用使用new申请内存空间就一定要使用delete释放内存空间。为什么来看下面一段代码A*pnewA[10];delete[]p;已知一个A类型的大小为 4 字节所以这里申请 10 个A类型的空间空间大小应该为 40 个字节。那这里真的是 40 个字节吗其实不是的这里在x86的环境下调试会发现这里申请了 44 个字节。那new多申请这 4 个字节的空间用来干什么其实是为了保存一共这块空间一共申请了多少个A类型的对象有的人会问保存这个干什么那如果没有保存这个数值编译器要怎么确定释放空间时需要调用多少次析构函数呢既然这多出来的 4 个字节是存储对象个数的那么这个空间的数值应该是 10我们可以使用内存窗口查看。所以这里就知道了当使用new申请多个对象的内存时它会在原本的空间前面多申请一个内存空间用来存储申请的对象的个数这里回到刚才的问题现在你或许就已经知道为什么new和delete需要配套使用了。小结由于new一次申请多个对象的空间会在空间前面多申请一个内存空间而如果使用free来释放空间它并不会将前面多申请的内存空间释放这样就会导致程序运行崩溃因此使用new申请的空间内存应该使用delete释放。️ 使用new[]申请多个对象时仅仅使用delete释放内存程序也会崩溃正确的是使用对应的delete[]四. operator new和operator delete函数1. new的底层——operator new函数在感叹new为何如此方便之时我们不禁问到new这么好用那它的底层是不是很复杂呢这里先说结论new的底层是operator new函数operator new 并不存在于标准库中而是一个全局函数我们可以通过C参考手册确定关于 operator new 函数的具体细节这里不过多赘述而是主要看 new 的底层实现这里可以通过调试的反汇编观察 new 的底层//自动调用构造函数List*ltnewList;//自动调用析构函数deletelt;继续进入 operator new 的函数的实现惊奇的发现operator new 的底层居然是malloc函数我们再来理清楚调用逻辑当使用 new 动态内存申请时new 会调用 operator new 函数而 operator new 函数本质上又还是通过 malloc 申请动态内存所以 new 的本质还是使用 malloc 动态申请内存。️ 使用 new 申请空间时构造函数的调用并不是封装在operator new函数的而是分开的。其实从刚刚的第一层反汇编就可以看到2. delete的底层——operator delete函数delete 的底层和 new 一样都是通过调用其他的函数实现的delete 对应的函数为operator delete追溯到最底层会发现delete 释放空间的功能是通过free实现的。这里也还同时验证了上面所说delete 是先调用析构函数再调用 operator delete 释放指针的内存的。小结new 是通过调用 operator new 申请空间的而 operator new 又是使用malloc 申请空间的所以本质上 new 还是使用 malloc 申请空间delete 是通过 operator delete 释放空间的本质上它还是使用free释放空间operator new 和 operator delete 都是全局函数五. 定位new表达式(placement-new)定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象其使用格式如下new(ptr)type;注 ptr是指向内存空间的指针 type是要调用的拷贝构造对应的类型定位 new 表达式一般是搭配内存池的使用这里在之后的篇章会讲解classA{public:A(inta1):_a(a){coutA(int a 1)endl;}~A(){_a0;cout~A()endl;}private:int_a;};intmain(){A*p(A*)malloc(sizeof(A));new(p)A;p-~A();free(p);//使用 operator new和malloc效果一样A*p1(A*)operatornew(sizeof(A));new(p1)A;p1-~A();operatordelete(p1);return0;}️ operator new 本质上也是调用malloc申请内存空间所以它的使用方式也和 malloc一样需要强制类型转换和以字节为单位开辟空间否则编译器会报错六. new/delete和malloc/free的区别语法区别new/delete不需要使用强制类型转换malloc/free需要new/delete申请空间不需要判断空指针malloc/free需要new/delete给多个元素申请空间不需要计算空间大小malloc/free需要new/delete可以在申请空间后直接初始化malloc/free不可以️ 这里说到使用new申请空间不需要判断空指针是因为new空间申请失败时会抛出异常使用catch接收异常就知道空间申请失败了。这里new抛异常在之后的篇章会讲解核心区别new/delete对自定义类型的内存申请和内存释放会自动调用对应的构造函数和析构函数malloc/free则没有这个功能本篇完结下期预告【C】string上