当前位置: 首页 > news >正文

【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[]: 编译器要知道调用多少次析构函数,不匹配会导致少调用析构或越界访问。


总结

欢迎点赞 + 收藏 + 关注!

http://www.cnnetsun.cn/news/3542953.html

相关文章:

  • 自动化脚本中js如何导入或调用其它js脚本
  • 我把向量数据库从 Milvus 切到 pgvector 后,检索 P99 从 230ms 压到 18ms:这 4 个取舍要注意
  • 用群晖给 ESXi 自动续期 Let‘s Encrypt 证书:三个官方文档没写的坑
  • 一文读懂PARD2-Llama-3.1-8B的Confidence-Adaptive Token技术:提升模型接受率的关键
  • ReAct 和 Plan and Solve 理解
  • 【Bug已解决】macOS detects Codex Computer Use.app as malware and deletes it! 解决方案
  • 鸿蒙Flutter Center与Align:组件对齐方式
  • 如何用Path of Building 2精准规划PoE2角色构建?3大核心功能深度解析
  • 逆变器芯片失效分析与防护设计实践
  • AI数据基础设施预计有1984亿规模?爱分析拆解七大细分市场构成
  • 163MusicLyrics:跨平台云音乐歌词获取与处理工具的深度解析
  • 旧款Mac免费升级macOS终极指南:用OpenCore Legacy Patcher重获新生
  • Markdown-Edit终极指南:Windows平台最简洁的Markdown编辑器完全解析
  • git-pr-release安全配置:保护你的GitHub Token和API访问完整指南
  • Codex全套科研技能汇总
  • React Native 跨平台图片浏览器开发:iOS 与 Android 兼容性指南
  • 如何3分钟打造专业级foobar2000美化方案:终极视觉与功能升级指南
  • C++字符编码转换实战:libiconv解决乱码问题
  • 成都网站建设木木科技:踩坑无数后,我为什么最终选了这家?
  • 怎么在网站上建设投票统计:从混乱到清晰的实战指南
  • 网站建设四段合一:告别割裂式开发,一次搞定设计与落地
  • 营销网站建设都是专业技术人员吗?别被忽悠了,这行水很深
  • Avalonia:3个核心模块实现跨平台.NET桌面应用的终极解决方案
  • Mac Mouse Fix:让你的普通鼠标在macOS上超越苹果触控板的终极方案
  • ISO 13355:2016是什么标准,ISO 13355随机振动试验包装测试
  • 075、语义分割驱动的局部调优:场景感知的影像增强
  • 数据库的概述--常用SQL命令
  • Runway官方未公布的12个生产力捷径:Ctrl+Shift+X触发的隐藏功能,仅限Beta测试者知晓
  • AI接口熔断与重试机制实战:Spring Retry与Sentinel应用
  • 进阶 XXE 漏洞攻防:从基础探测到高阶利用,附防御修复思路