news 2026/7/22 4:52:35

【C++初阶】内存管理总结(从 C 语言 malloc 到 C++ new/delete)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++初阶】内存管理总结(从 C 语言 malloc 到 C++ new/delete)

文章目录

  • 前言
  • 一、C/C++ 内存区域划分
    • 1. 五大内存区域
    • 2. 变量位置判断
  • 二、C 语言动态内存管理:malloc/calloc/realloc
    • 1. 函数对比
  • 三、C++ 内存管理:new 和 delete
    • 1. new/delete 操作内置类型
    • 2. new/delete 操作自定义类型
  • 四、底层揭秘:operator new 与 operator delete
    • 1. operator new 是什么?
    • 2. operator delete 是什么?
    • 3. 关系链:
  • 五、new 和 delete 的实现原理
    • 1. 内置类型
    • 2. 自定义类型
  • 总结

前言

本文从 C/C++ 内存分区讲起,简单回顾 C 语言malloc/calloc/realloc,再深入讲解 C++ new/delete的用法、底层原理。


一、C/C++ 内存区域划分

1. 五大内存区域

区域存储内容特点
栈(Stack)非静态局部变量、函数参数、返回值等函数结束自动释放,向下增长,空间较小
内存映射段动态库、文件映射、共享内存高效 I/O 映射,了解即可
堆(Heap)动态申请的内存(malloc/new)手动申请手动释放,向上增长,空间大
数据段(静态区)全局变量、静态变量(static)程序整个生命周期都存在,程序结束释放
代码段(常量区)可执行代码、只读常量(字符串常量)只读,不可修改

2. 变量位置判断

结合以下的代码,理解各个变量的存储位置。

intglobalVar=1;// 全局变量 → 数据段(静态区)staticintstaticGlobalVar=1;// 全局静态变量 → 数据段(静态区)voidTest(){staticintstaticVar=1;// 局部静态变量 → 数据段(静态区)intlocalVar=1;// 局部变量 → 栈intnum1[10]={1,2,3,4};// 局部数组 → 栈charchar2[]="abcd";// char2数组本身在栈,字符串常量"abcd"在代码段,数组拷贝了常量内容constchar*pChar3="abcd";// 指针pChar3在栈,指向的字符串"abcd"在代码段(常量区)int*ptr1=(int*)malloc(sizeof(int)*4);// ptr1指针在栈,指向的空间在堆int*ptr2=(int*)calloc(4,sizeof(int));// ptr2指针在栈,指向的空间在堆int*ptr3=(int*)realloc(ptr2,sizeof(int)*4);// ptr3指针在栈,指向的空间在堆free(ptr1);free(ptr3);}

易错点:
1. 栈 vs 堆
栈:函数调用自动分配,结束自动回收,速度快,空间小;
堆:手动申请手动释放,速度慢,空间大,使用灵活。

2. 字符串常量
"abcd"这种字符串字面量存在代码段(常量区),只读不可修改;
char char2[] = "abcd"是把常量拷贝到栈上的数组里,数组内容可以修改。

3. static 变量
不管是全局还是局部 static,都存在数据段,生命周期贯穿整个程序。

二、C 语言动态内存管理:malloc/calloc/realloc

C 语言管理堆内存靠四个函数:malloc、calloc、realloc、free。

1. 函数对比

函数作用初始化参数
malloc申请指定字节数的堆空间不初始化,内容随机总字节数
calloc申请指定个数指定大小的空间自动初始化为 0个数 + 单个大小
realloc对已有空间扩容不初始化新空间原指针 + 新总大小
free释放堆空间-指向堆空间的指针

知识点1:realloc:给已有空间扩容,分两种情况:
原地扩容:原空间后面有足够空位,直接扩大,返回原地址;
异地扩容:原空间后面不够,找一块更大的新空间,拷贝数据,释放旧空间,返回新地址。

知识点2:realloc 后原来的指针需要 free 吗?

int*p2=(int*)calloc(4,sizeof(int));int*p3=(int*)realloc(p2,sizeof(int)*10);// 这里需要 free(p2) 吗?

不需要!因为:
如果是原地扩容,p3和p2地址一样,free(p3)就释放了;
如果是异地扩容,realloc内部会自动释放旧空间p2,不用手动 free。

三、C++ 内存管理:new 和 delete

C 语言的 malloc/free 在 C++ 里依然能用,但对付自定义类型就很麻烦 —— malloc只开空间,不会调用构造函数;free只释放空间,不会调用析构函数。
于是,C++ 推出了自己的内存管理操作符:newdelete

1. new/delete 操作内置类型

对于int、char等内置类型,new/delete和malloc/free功能差不多,只是写法更简洁。

voidTest(){// 1. 申请单个int空间,不初始化int*ptr1=newint;// 2. 申请单个int空间,初始化为10int*ptr2=newint(10);// 3. 申请连续10个int的数组int*ptr3=newint[10];// 数组初始化:C++11支持列表初始化int*ptr4=newint[5]{1,2,3,4,5};// 释放单个空间deleteptr1;deleteptr2;// 释放数组空间,必须加 []delete[]ptr3;delete[]ptr4;}

重要匹配规则:

  • new对应delete
  • new[]对应delete[]

不匹配使用会导致内存泄漏甚至程序崩溃。

2. new/delete 操作自定义类型

这才是 new/delete 真正的价值:申请空间 + 调用构造函数 / 释放空间前调用析构函数

#include<iostream>usingnamespacestd;classA{public:A(inta=0):_a(a){cout<<"A():"<<this<<endl;}~A(){cout<<"~A():"<<this<<endl;}private:int_a;};intmain(){// C语言方式:只开空间,不调用构造A*p1=(A*)malloc(sizeof(A));free(p1);// 只释放空间,不调用析构// C++方式:开空间 + 调用构造函数A*p2=newA(1);deletep2;// 先调用析构,再释放空间cout<<"----------------"<<endl;// 数组版本A*p3=newA[2];// 调用2次构造函数delete[]p3;// 调用2次析构函数return0;}


核心结论

  • malloc/free:只负责开 / 释放空间,不管对象的构造和析构;
  • new/delete:
    new:先开空间,再调用构造函数初始化对象;
    delete:先调用析构函数清理资源,再释放空间。

这也是为什么 C++ 推荐用new/delete管理自定义类型。

四、底层揭秘:operator new 与 operator delete

new不是直接调用malloc,其实中间还有一层:operator new 和 operator delete 全局函数。

下面我们通过反汇编窗口查看:
operator new :

operator delete 还需要继续深入:


反汇编比较难理解,这里只是让大家看一下底层确实是调用了operator new 和 operator delete 全局函数,更多细节就不讨论了。

1. operator new 是什么?

operator new 是 C++ 提供的全局函数,不是运算符重载,它的本质就是封装了 malloc + 失败抛异常
可以把源码简单理解为:

void*operatornew(size_t size){void*p;// 循环调用malloc申请空间while((p=malloc(size))==0){// 申请失败,调用用户设置的应对措施if(_callnewh(size)==0){// 没有应对措施,抛出 bad_alloc 异常throwbad_alloc();}}returnp;}

关键区别:malloc 失败 VS new 失败

  • malloc 失败:返回NULL,使用前必须判空;
  • new 失败:抛出bad_alloc异常,不需要判空,需要捕获异常。

2. operator delete 是什么?

operator delete 同样是全局函数,底层封装了free,专门用来释放operator new申请的空间。

3. 关系链:

简单把关系捋一下,就形成了:
new 操作符 → 调用 operator new 全局函数 → 底层调用 malloc 申请空间 → 申请成功返回地址,失败抛异常
delete 操作符 → 先调用析构函数 → 调用 operator delete 全局函数 → 底层调用 free 释放空间

五、new 和 delete 的实现原理

1. 内置类型

对于int、double等内置类型:

  • newmalloc几乎一样,只是语法不同、失败处理不同;
  • deletefree几乎一样;
  • new[]申请连续空间,delete[]释放连续空间。

2. 自定义类型

new 的执行流程
调用operator new函数申请堆空间;
在申请好的空间上执行构造函数,完成对象初始化。

delete 的执行流程
在对象上执行析构函数,清理对象内部资源;
调用operator delete函数释放堆空间。

new T [N] 的执行流程
调用operator new[],底层调用operator new申请N个对象的总空间;
在空间上执行N次构造函数,逐个初始化每个对象。

delete [] 的执行流程
在空间上执行N次析构函数,逐个清理每个对象;
调用operator delete[],底层调用operator delete释放总空间。

这也解释了为什么new[]必须配delete[]: 编译器要知道调用多少次析构函数,不匹配会导致少调用析构或越界访问。


总结

欢迎点赞 + 收藏 + 关注!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/20 18:49:00

自动化脚本中js如何导入或调用其它js脚本

在编写自动化脚本时&#xff0c;随着业务逻辑日趋复杂&#xff0c;将不同功能封装到独立的JS脚本中、实现模块化编程&#xff0c;已成为提升代码可维护性和复用性的关键手段。冰狐智能辅助平台为开发者提供了三种原生API来实现脚本之间的导入与调用&#xff0c;分别是import静态…

作者头像 李华
网站建设 2026/7/20 18:48:46

用群晖给 ESXi 自动续期 Let‘s Encrypt 证书:三个官方文档没写的坑

目录环境为什么是「群晖跑 acme.sh SSH 推送」遇到的坑坑一&#xff1a;ESXi 不认 ed25519 密钥坑二&#xff1a;MULTI_CALL 不够&#xff0c;必须开 USE_SCP这个坑真正危险的地方坑三&#xff1a;auto-backup.sh 会把私钥打进日志另外几个小提醒最终脚本群晖任务计划配置验证…

作者头像 李华
网站建设 2026/7/20 18:41:09

ReAct 和 Plan and Solve 理解

1、区别的理解例子&#xff1a;预订一次从北京到上海的商务旅行&#xff0c;包括机票、酒店、租车它们之间存在依赖&#xff1a;航班到达机场↓ 影响酒店位置↓ 影响是否需要租车↓ 影响取车地点和时间还要同时考虑&#xff1a;出发和返程时间&#xff1b;会议时间&#xff1b;…

作者头像 李华