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

【C++ 学习 ⑥】- C++ 动态内存管理详解

目录

一、new 表达式和 delete 表达式的工作机理

二、operator new 和 operator delete 函数

2.1 - 标准库定义

2.2 - 重载

三、定位 new 表达式

四、常见面试题

4.1 - malloc/free 和 new/delete 的区别

4.2 - 内存泄漏


 

在 C++ 中,new 和 delete 既是关键字,也是一种特殊的操作符。C++ 程序中的动态内存管理主要就是通过操作符 new/delete 和 new[]/delete[] 实现的


一、new 表达式和 delete 表达式的工作机理

假设有一个类 A

class A
{
public:A(int x = 0) : _i(x) {cout << "A(int x = 0), _i: " << _i << endl;}~A(){cout << "~A(), _i: " << _i << endl;}
private:int _i;
};

当我们使用一条 new 表达式时

A* p1 = new A;  // 分配一个默认初始化的 A 类对象
A* p2 = new A(10);  // 分配并初始化一个 A 类对象
// A(int x = 0), _i: 0
// A(int x = 0), _i: 10
​
A* arr1 = new A[5];  // 分配五个默认初始化的 A 类对象
A* arr2 = new A[5]{ 1, 2, 3, 4, 5 };  // 分配并初始化五个 A 类对象
A* arr3 = new A[5]{ A(1), A(2), A(3), A(4), A(5) };  // 分配并初始化五个 A 类对象
// A(int x = 0), _i: 0 --> 输出 5 次
// A(int x = 0), _i: 1 ~ 5
// A(int x = 0), _i: 1 ~ 5

实际上执行了以下两步操作

  1. 调用一个名为 operator new(或者 operator new[])的标准库函数。该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或对象的数组)。

    operator new[] 实际上通过调用 operator new 完成 N 个对象空间的申请

  2. 编译器运行相应的构造函数以构造这些对象,并为其传入初始值

当我们使用一条 delete 表达式时

delete p1;  // 销毁 *p1,然后释放 p1 指向的内存空间
delete p2;  // 销毁 *p2,然后释放 p2 指向的内存空间
// ~A(), _i: 0
// ~A(), _i: 10
​
delete[] arr1;  // 销毁数组中的元素,然后释放对应的内存空间
delete[] arr2;  // 销毁数组中的元素,然后释放对应的内存空间
delete[] arr3;  // 销毁数组中的元素,然后释放对应的内存空间
// ~A(), _i: 0  --> 输出 5 次
// ~A(), _i: 5 ~ 1
// ~A(), _i: 5 ~ 1

实际上执行了以下两步操作

  1. 对 p1/p2 所指的对象或者 arr1/arr2/arr3 所指的数组中的元素执行对应的析构函数

  2. 编译器调用名为 operator delete(或者 operator delete[])的标准库函数释放内存空间

    operator delete[] 实际上通过调用 operator delete 完成内存空间的释放

 

 


二、operator new 和 operator delete 函数

2.1 - 标准库定义

operator new 函数实际上是通过 malloc 函数来申请空间的,如果 malloc 申请空间成功,则直接返回;如果申请空间失败,则尝试执行空间不足应对措施,如果用户设置了应对措施,则继续申请,否则抛异常

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出 bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}
  1. 抛出异常

    int main()
    {int* p = nullptr;do{p = new int[1024 * 1024];} while (p);return 0;
    }

  2. 捕获异常

    #include <iostream>
    using namespace std;
    ​
    int main()
    {int* p = nullptr;try{do{p = new int[1024 * 1024];} while (p);}catch (exception& e){cout << e.what() << endl;  // bad allocation}return 0;
    }

operator delete 函数实际上是通过 free 函数来释放空间的

void operator delete(void* pUserData)
{_CrtMemBlockHeader* pHead;
​RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
​if (pUserData == NULL)return;
​_mlock(_HEAP_LOCK);  /* block other threads */__TRY/* get a pointer to memory block header */pHead = pHdr(pUserData);
​/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse);
​__FINALLY_munlock(_HEAP_LOCK);  /* release other threads */__END_TRY_FINALLY
​return;
}
​
/* free 的实现 */
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

2.2 - 重载

一般情况下不需要自定义 operator new 和 operator delete,除非在申请和释放空间时有某些特殊的需求,例如:在使用 new 和 delete 申请和释放空间时,打印一些日志信息,可以简单帮助用户来检测是否存在内存泄漏

#include <iostream>
using namespace std;
​
// 重载 operator delete,在申请空间时:打印在哪个文件、哪个函数、第多少行,申请了多少个字节
void* operator new(size_t size, const char* fileName, const char* funcName,size_t lineNo)
{void* p = ::operator new(size);cout << fileName << "-" << funcName << "-" << lineNo << "-" << p << "-"<< size << endl;return p;
}
​
// 重载 operator delete,在释放空间时:打印再那个文件、哪个函数、第多少行释放
void operator delete(void* p, const char* fileName, const char* funcName,size_t lineNo)
{cout << fileName << "-" << funcName << "-" << lineNo << "-" << p <<endl;::operator delete(p);
}
​
int main()
{// 对重载的operator new 和 operator delete进行调用int* p = new(__FILE__, __FUNCTION__, __LINE__) int;operator delete(p, __FILE__, __FUNCTION__, __LINE__);return 0;
}

上述调用显然太麻烦了,可以使用宏对调用进行简化(只有在 Debug 模式下,才调用用户重载的 operator new 和 operator delete)

#ifdef _DEBUG
#define new new(__FILE__, __FUNCTION__, __LINE__)
#define delete(p) operator delete(p, __FILE__, __FUNCTION__, __LINE__)
#endif
​
int main()
{int* p = new int;delete(p);return 0;
}


三、定位 new 表达式

定位 new(placement new)表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式

new(place_address) type
// 或者
new(place_address) type(initializer-list)
// place_address 必须是一个指针,initializer-list 是初始化列表

使用场景

定位 new 表达式在实际中一般是配合内存池使用,因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用定位 new 表达式进行显示调用构造函数进行初始化。

#include <iostream>
using namespace std;
​
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0), _i: " << _i << endl;}~A(){cout << "~A(), _i: " << _i << endl;}
private:int _i;
};
​
int main()
{// A* p = (A*)malloc(sizeof(A));// 或者A* p = (A*)operator new(sizeof(A));new(p) A(10);// A(int x = 0), _i: 10
​p->~A();// ~A(), _i: 10free(p);return 0;
}


四、常见面试题

4.1 - malloc/free 和 new/delete 的区别

malloc/free 和 new/delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同点是:

  1. malloc 和 free 是函数,new 和 delete 是操作符;

  2. malloc 申请的空间不会初始化,new 可以初始化;

  3. malloc 申请空间时,需要手动计算空间大小并传递,new 只需要在其后跟上空间的类型即可,如果是多个对象,则在 [] 中指定对象个数即可;

  4. malloc 的返回值为 void*,在使用时必须强转,new 不需要,因为 new 后跟的是空间的类型;

  5. malloc 申请空间失败时,返回的是 NULL,因此使用前必须判空,new 不需要,但是 new 需要捕获异常;

  6. 申请自定义类型对象时,malloc/free 只会开辟和释放空间,不会调用构造与析构函数,而 new 在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理。

4.2 - 内存泄漏

内存泄漏指因为疏忽或者错误造成程序未能释放已经不能使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等,出现内存泄漏会导致响应越来越慢,最终卡死。

C/C++ 程序中我们一般关心两种方面的内存泄漏:

  1. 堆内存泄漏(Heap Leak)

    堆内存指的是程序执行中依据需要通过 malloc/ calloc/ realloc/ new 等从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak。

  2. 系统资源泄漏

    指程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

相关文章:

【C++ 学习 ⑥】- C++ 动态内存管理详解

目录 一、new 表达式和 delete 表达式的工作机理 二、operator new 和 operator delete 函数 2.1 - 标准库定义 2.2 - 重载 三、定位 new 表达式 四、常见面试题 4.1 - malloc/free 和 new/delete 的区别 4.2 - 内存泄漏 在 C 中&#xff0c;new 和 delete 既是关键字&…...

【5.21】六、自动化测试—常见技术

目录 6.2 自动化测试常见技术 1. 录制与回放测试 2. 脚本测试 3. 数据驱动测试 6.2 自动化测试常见技术 自动化测试技术有很多种&#xff0c;这里介绍3种常见的技术&#xff1a; 1. 录制与回放测试 录制是指使用自动化测试工具对桌面应用程序或者是Web页面的某一项功能进…...

JavaScript中的事件循环机制,包括事件循环的原理、宏任务和微任务、事件队列和调用栈、以及如何优化事件循环

JavaScript中的事件循环机制是JavaScript运行引擎的核心之一&#xff0c;它决定了代码的执行方式和效率。本文将从几个方面介绍JavaScript中的事件循环机制&#xff0c;包括事件循环的原理、宏任务和微任务、事件队列和调用栈、以及如何优化事件循环。 一、事件循环的原理 事…...

【华为OD机试c++】解压报文【2023 B卷 |200分】

题目描述 为了提升数据传输的效率&#xff0c;会对传输的报文进行压缩处理。 输入一个压缩后的报文&#xff0c;请返回它解压后的原始报文。 压缩规则&#xff1a;n[str]&#xff0c;表示方括号内部的 str 正好重复 n 次。 注意 n 为正整数&#xff08;0 < n < 100&a…...

JS中Array的forEach、map、filter方法区别?

一&#xff1a;基本用法 1、forEach()函数用于对数组中的每个元素执行给定的函数&#xff0c;而它不返回任何值&#xff0c;它只是对每个元素调用传入的函数。这个函数可以接受三个参数&#xff1a;当前元素的值、当前元素的索引和整个数组。 const arr [1, 2, 3]; arr.forE…...

Java的Arrays类的sort()方法(41)

目录 sort&#xff08;&#xff09;方法 1.sort&#xff08;&#xff09;方法的格式 2.使用sort&#xff08;&#xff09;方法时要导入的类 3.作用 4.作用的对象 5.注意 6.代码及结果 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;结果 sort&#xff08;&…...

Redis安装及其配置文件修改

一、redis 安装 点击即可下载 https://download.redis.io/releases/ 将下载后的包通过xftp上传到服务器 解压&#xff0c;我这边是解压到/usr/local目录下 -- 创建路径 mkdir /usr/local/redis -- 解压 tar -zxvf redis-4.0.0.tar.gz -C /usr/local/redis 为防止编译失败&am…...

VSOMEIP3抓包数据

环境 $ cat /etc/os-release NAME"Ubuntu" VERSION"20.04.6 LTS (Focal Fossa)" IDubuntu ID_LIKEdebian PRETTY_NAME"Ubuntu 20.04.6 LTS" VERSION_ID"20.04" HOME_URL"https://www.ubuntu.com/" SUPPORT_URL"https:/…...

基于PyQt5的图形化界面开发——Windows内存资源监视助手[附带编译exe教程]

基于PyQt5的图形化界面开发——Windows内存资源监视助手[附带编译exe教程] 0. 前言1. 资源信息获取函数——monitor.py2. UI界面——listen.py3. main.py4. 运行效果5. 编译 exe 程序6. 其他PyQt文章 0. 前言 利用 PyQt5 开发一个 windows 的资源监视助手&#xff0c;在使用虚…...

Linus Torvalds发布了第一个Linux内核6.4候选版本

导读自Linux内核6.3发布和下一个内核系列Linux 6.4的合并窗口开放以来&#xff0c;已经过去了一段时间&#xff0c;近日&#xff0c;Linus Torvalds发布了第一个RC&#xff08;候选发布版&#xff09;的里程碑&#xff0c;供公众测试。 为期两周的Linux内核6.4合并窗口现已关闭…...

由浅入深Dubbo核心源码剖析环境介绍

目录 1 框架介绍1.1 概述1.2 运行架构1.3 整体设计 2 环境搭建2.1 源码拉取2.2 源码结构2.3 环境导入2.4 测试2.5 管理控制台 1 框架介绍 1.1 概述 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架&#xff0c;使得应用可通过高性能的 RPC 实现服务的输出和输入功能&#…...

Java 远程连接 SQLite 数据库

Java 可以使用 JDBC API 来连接 SQLite 数据库。但是&#xff0c;SQLite 不支持远程连接&#xff0c;因为它是一种文件数据库&#xff0c;需要直接访问数据库文件。 如果您需要从远程位置访问 SQLite 数据库&#xff0c;可以将 SQLite 数据库文件放在共享文件夹中&#xff0c;…...

网安面试题大全(附答案)

本文面试题汇总&#xff1a; 防范常见的 Web 攻击 重要协议分布层 arp协议的工作原理 rip协议是什么&#xff1f;rip的工作原理 什么是RARP&#xff1f;工作原理 OSPF协议&#xff1f;OSPF的工作原理 TCP与UDP区别总结 什么是三次握手四次挥手&#xff1f; tcp为什么要三次握手…...

windows 系统扩容C盘注意事项

windows系统大家都不陌生&#xff0c;是大家用的最多的操作系统。在实际的使用中&#xff0c;遇到需要扩容C盘的情况不是很多&#xff0c;但是如果遇到了&#xff0c;有以下几个事项需要大家注意&#xff1a; 剩余空间是否充足 不论当前服务器是物理服务器还是虚拟机&#xff…...

接入支付宝沙箱环境

1、这里有几个重要数据要拿到&#xff0c;一个是支付宝的公钥和私钥&#xff0c;一个是支付的网关&#xff0c;和支付的APPID。这几个数据是要写到代码中的 官方手册&#xff1a;文档地址 1.1 配置沙箱应用环境 https://openhome.alipay.com/develop/sandbox/app 1.2 配置接口…...

用原生JS实现虚拟列表(IT枫斗者)

用原生JS实现虚拟列表 介绍 最近在开发需求的时候&#xff0c;有用到 Antd 的虚拟列表组件 rc-virtual-list &#xff0c;粗略地看了一下源码&#xff0c;于是萌生了自己写一个虚拟列表的想法。当一个列表需要渲染大量数据的时候是非常耗时的&#xff0c;而且在列表滚动的过程…...

FAT NTFS Ext3文件系统有什么区别

10 年前 FAT 文件系统还是常见的格式&#xff0c;而现在 Windows 上主要是 NTFS&#xff0c;Linux 上主要是Ext3、Ext4 文件系统。关于这块知识&#xff0c;一般资料只会从支持的磁盘大小、数据保护、文件名等各种维度帮你比较&#xff0c;但是最本质的内容却被一笔带过。它们最…...

信息收集思路

1、开发者注释 在网站前端代码中遗留的开发者注释 其中可能包含某些关键信息 ​ &#x1f4a1; 使用F12 、CtrlU 、view-source: 查看前端源码 ​ 3、Robots文件 爬虫协议&#xff0c;网站根目录存在的robots.txt文件&#xff0c;用于告知搜索引擎或爬虫哪些路径和页面不…...

Tauri应用开发(二):创建第一个Tauri应用

创建tauri应用 推荐参考官方文档&#xff1a;https://tauri.app/v1/guides/ 创建命令&#xff1a; npm create tauri-applatest&#x1f4a1;注意&#xff1a;请确保Node.js和Rust已经正确安装 在创建过程中&#xff0c;需要根据提示选择配置项。 主要配置有&#xff1a; 项目…...

自信裸辞:一晃 ,失业都3个月了.....

最近&#xff0c;找了很多软测行业的朋友聊天、吃饭 &#xff0c;了解了一些很意外的现状 。 我一直觉得他们技术非常不错&#xff0c;也走的测开/管理的路径&#xff1b;二三月份裸辞的&#xff0c;然后一直在找工作&#xff0c;现在还没找到工作 。 经过我的分析&#xff0…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

Pandas 可视化集成:数据科学家的高效绘图指南

为什么选择 Pandas 进行数据可视化&#xff1f; 在数据科学和分析领域&#xff0c;可视化是理解数据、发现模式和传达见解的关键步骤。Python 生态系统提供了多种可视化工具&#xff0c;如 Matplotlib、Seaborn、Plotly 等&#xff0c;但 Pandas 内置的可视化功能因其与数据结…...

使用 uv 工具快速部署并管理 vLLM 推理环境

uv&#xff1a;现代 Python 项目管理的高效助手 uv&#xff1a;Rust 驱动的 Python 包管理新时代 在部署大语言模型&#xff08;LLM&#xff09;推理服务时&#xff0c;vLLM 是一个备受关注的方案&#xff0c;具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…...

【Pandas】pandas DataFrame dropna

Pandas2.2 DataFrame Missing data handling 方法描述DataFrame.fillna([value, method, axis, …])用于填充 DataFrame 中的缺失值&#xff08;NaN&#xff09;DataFrame.backfill(*[, axis, inplace, …])用于**使用后向填充&#xff08;即“下一个有效观测值”&#xff09…...