当前位置: 首页 > 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…...

物联网DDoS检测:XGBoost、KNN、SGD与朴素贝叶斯性能对比

1. 项目概述&#xff1a;当物联网遇上DDoS&#xff0c;我们如何用机器学习“看门”&#xff1f;在网络安全这个没有硝烟的战场上&#xff0c;DDoS攻击一直是让运维和架构师们头疼的“流量洪水”。传统的防御手段&#xff0c;比如基于固定阈值的流量清洗或者已知攻击特征的签名匹…...

SRWE:5分钟掌握Windows窗口任意调整的终极方案

SRWE&#xff1a;5分钟掌握Windows窗口任意调整的终极方案 【免费下载链接】SRWE Simple Runtime Window Editor 项目地址: https://gitcode.com/gh_mirrors/sr/SRWE 你是否曾经因为Windows应用程序的窗口限制而感到束手无策&#xff1f;想要截取超高分辨率的游戏画面却…...

79万中文医疗对话数据集:构建智能医疗问答系统的核心技术资源

79万中文医疗对话数据集&#xff1a;构建智能医疗问答系统的核心技术资源 【免费下载链接】Chinese-medical-dialogue-data Chinese medical dialogue data 中文医疗对话数据集 项目地址: https://gitcode.com/gh_mirrors/ch/Chinese-medical-dialogue-data 在医疗人工智…...

Golang JWT生产实践:时间精度、密钥轮换与Refresh Token安全设计

1. 这不是“加个Token就完事”的简单活儿 Golang领域JWT——这六个字背后&#xff0c;藏着太多人踩过坑、重写过三遍、上线后半夜被报警电话叫醒的真实故事。我第一次在生产环境用JWT做身份验证时&#xff0c;自信满满地照着某篇教程写了20行代码&#xff0c;结果上线第三天&am…...

3分钟快速解密Navicat密码:开源工具终极指南

3分钟快速解密Navicat密码&#xff1a;开源工具终极指南 【免费下载链接】navicat_password_decrypt 忘记navicat密码时,此工具可以帮您查看密码 项目地址: https://gitcode.com/gh_mirrors/na/navicat_password_decrypt 当您忘记Navicat中保存的数据库连接密码时&#…...

k6性能测试实战:现代工程化压测方法论

1. 为什么是k6&#xff0c;而不是JMeter或Gatling&#xff1f;我第一次在生产环境压测中被JMeter拖垮&#xff0c;是在一个电商大促前夜。当时用20台云服务器搭起分布式集群&#xff0c;配置文件写了300多行&#xff0c;结果一跑起来内存飙到95%&#xff0c;GC频繁&#xff0c;…...

高能物理数据分析实战:从W玻色子截面测量到机器学习应用

1. 项目概述&#xff1a;从海量对撞数据到物理发现如果你对宇宙的构成充满好奇&#xff0c;想知道我们是如何发现希格斯玻色子&#xff0c;或者顶夸克的质量是如何被精确测量的&#xff0c;那么高能物理数据分析就是你正在寻找的钥匙。这听起来可能离日常生活很远&#xff0c;但…...

多任务学习优化文档级机器翻译:源语句重建与上下文重建策略对比

1. 项目概述&#xff1a;当翻译需要“瞻前顾后”在机器翻译领域&#xff0c;我们早已告别了那个逐字逐句、生硬拼接的时代。基于Transformer架构的神经机器翻译&#xff08;NMT&#xff09;模型&#xff0c;凭借其强大的序列建模能力&#xff0c;已经能够产出相当流畅、准确的句…...

Leslie矩阵建模:从种群动力学到捕食竞争与机器学习拟合

1. 项目概述&#xff1a;从矩阵视角看种群兴衰在生态学和种群生物学里&#xff0c;我们总想预测未来&#xff1a;这片森林里的鹿群十年后会怎样&#xff1f;引入狼群后&#xff0c;整个系统会稳定还是崩溃&#xff1f;传统微分方程模型&#xff08;比如经典的Lotka-Volterra方程…...

AI Agent Harness Engineering:大模型之后的下一个技术爆发点

AI Agent Harness Engineering&#xff1a;大模型之后的下一个技术爆发点一、引言 1.1 钩子&#xff1a;从“大模型的局限性”到“人类解放双手的终极形态” 你是否有过这样的经历&#xff1f; 上周为了赶一份季度数据分析报告&#xff0c;你打开了GPT-4&#xff1a;先让它帮你…...