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

C/C++:动态内存管理

目录

一. C/C++内存分布

二. C/C++动态内存管理

2.1 C语言动态内存管理

2.2 C++动态内存管理

2.2.1 new/delete操作符

2.2.2 operator new与operator delete函数

2.3 new/delete的实现原理

2.4 定位new(placement - new)

2.5 new/delete和malloc/free的区别

三. 内存泄漏

3.1 什么是内存泄漏

3.2 内存泄漏的危害


一. C/C++内存分布

对于任何一个C/C++程序,其程序内存可以划分为下面几个区域:

  • 内核空间:用户代码不能读写
  • 栈区:存储非静态的局部变量、函数参数和返回值等。
  • 内存映射段:用于装载一个共享的动态内存库,用户可使用系统接口创建共享内存,做进程间通信。
  • 堆区:如果程序中需要用到较大的内存区域,则用户可以手动在堆区申请内存空间。
  • 数据段:用于存储全局数据和静态数据。
  • 代码段:用于存储可执行代码和只读常量。

图1.1以一段具体的程序为例,表述了每块内存区域存储的内容。

图1.1  C/C++程序内存区域分布示意图

二. C/C++动态内存管理

2.1 C语言动态内存管理

涉及四个主要函数,分别为malloc、calloc、realloc、free

  • malloc:动态申请一块内存空间,不进行初始化,函数原型为void* malloc(size_t size),在使用malloc函数的返回值是要进行强制类型转换。
  • calloc:动态申请内存空间,并将申请的空间的内容初始化为0。函数原型为:void* calloc(size_t num, size_t size),num表示申请内存空间的块数,size表示每块空间的大小。
  • realloc:调整一块已经申请的内存空间的大小,函数原型为void* realloc(void* ptr, size_t size),其中ptr指向要调整大小的内存空间、size表示调整后的内存空间大小。
  • free:释放动态申请的内存空间。

2.2 C++动态内存管理

2.2.1 new/delete操作符

在C++中,可以使用new来申请堆区内存空间,采用delete释放堆区内存空间,new的使用语法为:

  • 申请单块内存空间不初始化:数据类型* ptr = new 数据类型
  • 申请数组空间不初始化:数据类型* ptr = new 数据类型[数据量]
  • 申请单块内存空间并初始化:数据类型* ptr = new 数据类型(初始化值)
  • 申请数组空间并初始化:数据类型* ptr = new 数据类型{初始化值1, 初始化值2, ...... }

delete的使用语法为:

  • 释放单个内存空间:delete 指向动态开辟的内存区域的指针
  • 释放数组空间:delete[] 指向动态开辟的内存区域的指针 -- 其中[]就表示数组
int main()
{int* ptr1 = new int;  //申请单个int型空间int* ptr2 = new int(5);  //申请单个int型空间并初始化为5int* ptr3 = new int[3];  //申请3个int型空间int* ptr4 = new int[3]{ 1,2,3 };  //申请3个int型空间并初始化为1、2、3delete ptr1;delete ptr2;   //释放单个内存空间delete[] ptr3;delete[] ptr4;  //释放数组内存空间return 0;
}

当使用new/delete申请和释放内置数据类型的空间时,其与malloc/free没有除语法以外的太大区别。但是,对于自定义类型(类)空间,使用new申请空间会调用类的构造函数,使用delete释放空间会调用类的析构函数。如果使用malloc/free申请和释放内存空间,则不会调用类的构造函数和析构函数。

int main()
{//动态开辟类A的空间//使用new动态申请自定义类型的空间时,会调用自定义类型的构造函数//malloc-free自定义类型内存空间不会调用其构造函数和析构函数A* p1 = new A;A* p2 = new A[3];//delete自定义类型的内存空间是,会调用其析构函数delete p1;delete[] p2;  //释放动态开辟的内存空间A* p3 = new A(10, 20);  //申请类A型的内存空间并初始化//申请3个类A型的内存空间并初始化//注意:(10,20)会被解读为逗号表达式20,相当于只有构造函数的第一个参数给了值,第二个参数使用默认值//(30,40)、(50,60)同理A* p4 = new A[3]{ (10,20), (30, 40), (50, 60) }; //给构造函数传两个参数初始化要使用花括号{}而非小括号()A* p5 = new A[3]{ {10,20}, {30, 40}, {50, 60} };delete p3;delete[] p4;delete[] p5;return 0;
}

2.2.2 operator new与operator delete函数

对于一门语言,其处理异常的方式可能是下面两种:

  • 面向过程的语言(C) :返回值 + 错误码errno
  • 面向对象的语言(C++、JAVA等):抛异常 -- 可使用try catch来捕获异常

如C语言中的malloc函数,如果malloc函数申请动态内存失败,那么函数就返回空指针NULL,而operator new是C++提供的动态内存申请函数,其底层就是通过调用malloc函数来实现的。而为了满足面向对象的语言出错抛异常的要求,如果动态内存开辟失败,operator new会把malloc的返回值报错方式转换为抛出异常。

operator new的使用语法:数据类型* ptr = (数据类型*)operator new(空间大小)

operator delete与operator new配对使用,释放动态申请的内存空间。

注意:operator new动态申请自定义类型内存空间不调用构造函数。

int main()
{int* ptr1 = (int*)operator new(3 * sizeof(int));  //开辟3个int型空间A* ptr2 = (A*)operator new(sizeof(A));  //开辟类A型空间operator delete(ptr1);operator delete(ptr2);return 0;
}

如果采用operator new动态申请的空间过大,那么程序就会抛出异常。演示代码2.1采用operator new函数申请一块大小为2G的内存空间(申请空间过大导致失败),采用try - catch捕获异常,运行程序输出bad allocation。

演示代码2.1:

int main()
{char* ptr = nullptr;try{ptr = (char*)operator new(1024u * 1024u * 1024u * 2u);}catch (const exception& e){cout << e.what() << endl;}return 0;
}
图2.1 演示代码2.1的运行结果

如果程序抛出异常,那么其执行流会马上跳转到catch的位置处,哪怕是跨函数,也会跳转,并且,catch后面的语句会照常执行,知道程序结束或程序崩溃。演示代码2.2在函数func中申请了一块2G的内存空间,在申请完空间后输出"operator new successful",但是,由于期望申请的空间过大,导致申请失败,因此,程序不会输出"operator new successful",而是会直接跳转执行catch中的程序,输出bad allocation。

演示代码2.2:

char* func()
{char* ptr = (char*)operator new(1024u * 1024u * 1024u * 2u);cout << "operator new successful" << endl;return ptr;
}int main()
{char* ptr = nullptr;try{ptr = func();}catch(const exception& e){cout << "operator new fail" << endl;cout << e.what() << endl;}delete ptr;return 0;
}
图2.2 演示代码2.2运行结果

2.3 new/delete的实现原理

new/delete的底层是通过调用operator new和operator delete来实现的。

  • new的实现原理:(1)调用operator new申请内存空间  (2)调用自定义类型的构造函数
  • delete的实现原理:(1)调用自定义类型的析构函数  (2)调用operator delete释放内存空间
  • new[]的实现原理:(1)调用operator new[]申请空间  (2)调用自定义类型的构造函数
  • delete[]的实现原理:(1)调用自定义类型的析构函数  (2)调用operator delete[]释放空间
图2.3 new(左)和delete(右)的实现原理示意图

2.4 定位new(placement - new)

使用operator new申请动态内存空间,不会调用自定义类型的构造函数。但是有时候我们希望在operator new函数申请的空间上调用构造函数,可构造函数却不支持直接显式调用,这是就需要用到定位new来实现。

定位new使用语法:new(指向动态开辟的内存空间的指针)类名(传给构造函数的参数)

虽然构造函数不能显示调用,但是析构函数可以显示调用。

演示代码2.3:

class A
{
public:A(int a1 = 10, int a2 = 20, double a3 = 20.2): _a1(a1), _a2(a2), _a3(a3){std::cout << "A(int a1 = 10, int a2 = 20, double a3 = 20.2)" << std::endl;}~A(){std::cout << "~A()" << std::endl;}private:int _a1;int _a2;double _a3;
};int main()
{A* pa1 = nullptr;A* pa2 = nullptr;try{pa1 = (A*)operator new(sizeof(A)); pa2 = (A*)operator new(sizeof(A));}catch (const std::exception& e){std::cout << e.what() << std::endl;}//类的构造函数不能显示调用new(pa1)A;new(pa2)A(20, 30, 40.4);//析构函数可以显示调用(*pa1).~A();(*pa2).~A();operator delete(pa1);return 0;
}
图2.4 演示代码2.3的运行结果

2.5 new/delete和malloc/free的区别

  • 用法区别:
  1. malloc需要手动计算申请空间的大小,new只需要在数据类型后面的[]内输入所要申请类型空间的个数即可。
  2. malloc的返回值类型为void*,需要强制类型转换,而new在使用时会显示说明申请空间的类型,无需强制类型转换。
  • 底层区别:
  1. malloc/free是函数,new/delete是操作符。
  2. malloc申请空间失败返回NULL,需要判空,而new申请空间失败会抛异常,需要使用catch捕获异常。
  3. 申请自定义类型空间时,malloc/free只会申请空间,不会调用构造函数和析构函数,new/delete不但会申请和释放空间,还会调用默认构造函数完成初始化以及调用析构函数进行对象资源的清理。

三. 内存泄漏

如果申请了动态内存空间却不手动释放,就会造成内存泄漏。

3.1 什么是内存泄漏

动态申请内存空间,不使用了,但却没有释放,就存在内存泄漏,使可用内存越来越少。

3.2 内存泄漏的危害

  • 对于正常结束的进程,进程结束时泄漏掉的内存会自动还给系统,不会有太大危害。
  • 对于非正常结束的进程,如僵尸进程,以及需要长期运行的程序,如服务器程序,出现内存泄漏的危害就很大,系统会变得越来越慢,甚至卡死宕机。

所以,动态申请的内存空间一定要记得释放!释放动态内存使用的函数(操作符)一定要与申请内存时用的函数(操作符)匹配:malloc--free、new -- delete、new[] -- delete[]。

相关文章:

C/C++:动态内存管理

目录 一. C/C内存分布 二. C/C动态内存管理 2.1 C语言动态内存管理 2.2 C动态内存管理 2.2.1 new/delete操作符 2.2.2 operator new与operator delete函数 2.3 new/delete的实现原理 2.4 定位new&#xff08;placement - new&#xff09; 2.5 new/delete和malloc/free的…...

黑猫带你学eMMC协议第28篇:eMMC的开漏和推挽模式(push-pull open drain)

本文依据eMMC JEDEC5.1及个人工作经验整理而成,如有错误请留言。 文章为个人辛苦整理,付费内容,已加入原创侵权保护,禁止私自转载。 文章所在专栏:《黑猫带你学:eMMC协议详解》 1 什么是开漏和推挽 1.1 推挽电路是什么 关于推挽和开漏电路,更多介绍详见我的另一篇文章…...

simulink PID控制

系列文章目录 文章目录系列文章目录前言一、非线性系统线性化原理二、反馈控制开环控制反馈or闭环控制PID ControllerPID微调案例总结前言 将非线性系统近似线性化PIDblock与微调 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、非线性系统线性化 …...

如何在for循环内执行异步操作

var定义的i是全局的&#xff0c;每次遍历都会覆盖&#xff0c;最后i的值为10&#xff0c;所以输出10次10 for (var i 0; i < 10; i) {setTimeout(function () {console.log(i) // 输出10遍10}, 1000 i * 100)}setTimeOut 第三个函数 for (var i 0; i < 10; i) {setT…...

性能测试——LoadRunner: Controller的使用

Controller Controller是用来创建测试环境&#xff0c;执行在VUG中编写的测试脚本 可以直接点击Controller的快捷方式打开,也可以在VUG中打开 这里将虚拟用户数设置为3,比较适合自己的电脑性能 整个controller分为下面几个模块 这里先设置左下角的目标计划 设置初始化:双击…...

ChatGPT解答:纯前端文档预览,Vue实现,无需后端,支持Word、Excel、PPT、pdf、文本、图片,附接入demo和文档

ChatGPT解答&#xff1a;纯前端文档预览&#xff0c;Vue实现&#xff0c;无需后端&#xff0c;支持Word、Excel、PPT、pdf、文本、图片&#xff0c;附接入demo和文档 ChatGPTDemo Based on OpenAI API (gpt-3.5-turbo). 纯前端文档预览&#xff0c;Vue实现&#xff0c;无需后…...

刷题记录:牛客NC13950 Alliances 到树上联通点集的最短距离

传送门:牛客 题目描述: 题目较长,此处省略 输入: 7 1 2 1 3 2 4 2 5 3 6 3 7 2 2 6 7 1 4 3 5 1 2 1 1 1 5 2 1 2 输出: 2 1 1一道比较复杂的树题.需要一些复杂的讨论以及LCA知识 对于LCA,可以使用树链剖分进行解决 然后我们看一下题目,我们会发现有这样一个简单的结论,那就…...

行为型模式 - 状态模式State

学习而来&#xff0c;代码是自己敲的。也有些自己的理解在里边&#xff0c;有问题希望大家指出。 个人理解&#xff1a;感觉像桥接模式 代理模式。不知道这么想对不对&#xff0c;还希望笔记在放出后&#xff0c;有大佬彻底了解了给我解解惑。 策略模式的定义与特点 策略&…...

电视剧《狂飙》太过诡异,主演各个悄无声息,龙套演员却身价倍增

说起电视剧《狂飙》&#xff0c;相信很多人都有过观看&#xff0c;这部以反腐为题材的大剧&#xff0c;尺度之大近年来绝无仅有。不过观众在被剧情震撼的同时&#xff0c;也发现了一些诡异的事情&#xff0c;比如说主角和配角的反差&#xff0c;让人感觉很不适应。 在电视剧《狂…...

【微信小程序】-- 案例 - 本地生活(二十)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…...

LeetCode 每日一题 2023/2/27-2023/3/5

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录2/27 1144. 递减元素使数组呈锯齿状2/28 2363. 合并相似的物品3/1 2373. 矩阵中的局部最大值3/2 面试题 05.02. 二进制数转字符串3/3 1487. 保证文件名唯一3/4 982. 按位与为…...

SpringMVC中JSON数据的设置、RestFul风格

Java知识点总结&#xff1a;想看的可以从这里进入 目录3.4、JSON数据3.4.1、前端使用3.4.2、后端使用1、Jackson2、fastjson3.5、RestFul风格3.5.1、简介3.5.2、使用3.4、JSON数据 3.4.1、前端使用 前端在JavaScript中有封装的JSON对象&#xff0c;可以直接用来操作JSON数据。…...

Clion连接Docker,使用HElib库

文章目录需求Clion连接服务器内的DockerDockerCLionDocker内配置HElib库参考需求 HElib库是用C编写的同态加密开源库&#xff0c;一般在Linux下使用为了不混淆生产环境&#xff0c;使用Docker搭建HElib运行环境本地在Windows下开发&#xff0c;使用的IDE为Clion&#xff0c;本…...

go网络编程-websocket

1. WebSocket编程 文章目录1. WebSocket编程1.1.1. webSocket是什么1.1.2. 举个聊天室的小例子server.go文件代码hub.go文件代码data.go文件代码local.html文件代码1.1.1. webSocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之…...

Microsoft designer 使用教程

继各种ai绘图软件诞生之后 dell 2 playground.... 微软自己研发的重量级产品 Microsoft designer 上线了 Microsoft Designer 是微软公司推出的一款设计工具&#xff0c;主要用于快速创建Web和移动应用程序的原型设计。它提供了一系列的工具和模板&#xff0c;可以帮助用户…...

《Docker系列》Docker容器修改配置文件后,重启失败,如何修改配置并启动容器?

Docker容器修改配置文件后&#xff0c;重启失败&#xff0c;如何修改配置并启动容器&#xff1f; docker部署的MySQL容器&#xff0c;修改了my.cnf配置文件&#xff0c;重启的时候导致无法启动 通过查日志发现&#xff0c;配置文件中的binlog-db-dbhw写错了&#xff0c;应该是…...

遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器有个共同的局限性&#xff1a;他们都不能很好地扩展到大量的可选参数。比如用一个类表示包装食品外面显示的营养成分标签&#xff08;包括必选域和可选域&#xff09;。 重叠构造器 对于这样的类一般习惯采用重叠构造器&#xff08;telescoping constructor&…...

【Storm】【五】Storm集成Kafka

Storm集成Kafka 一、整合说明二、写入数据到Kafka三、从Kafka中读取数据一、整合说明 Storm 官方对 Kafka 的整合分为两个版本&#xff0c;官方说明文档分别如下&#xff1a; Storm Kafka Integration : 主要是针对 0.8.x 版本的 Kafka 提供整合支持&#xff1b;Storm Kafka …...

GVRP-LNP-VCMP讲解

目录 GVRP讲解 动态创建Vlan并将端口加入Vlan GVRP消息类型 GVRP工作原理 LNP讲解 动态修改接口链路类型 VCMP讲解 动态创建Vlan 相关概念 Vlan同步 VCMP与GVRP的区别 GVRP讲解 动态创建Vlan并将端口加入Vlan GVRP&#xff08;GARR Vlan Registration Protocol&#xf…...

28个精品Python爬虫实战项目

先来说说Python的优势&#xff01;然后给大家看下这28个实战项目的实用性&#xff01;Python跟其他语言相比&#xff0c;有以下优点&#xff1a;1. 简单Python是所有编程语言里面&#xff0c;代码量最低&#xff0c;非常易于读写&#xff0c;遇到问题时&#xff0c;程序员可以把…...

相信人还是相信ChatGPT,龙测首席AI专家给出了意料之外的答案

最近&#xff0c;关于ChatGPT的话题太火了&#xff01;各大社交软件都是他的消息&#xff01;从去年12月份ChatGPT横空出世&#xff0c;再到近期百度文心一言、复旦Moss的陆续宣布&#xff0c;点燃了全球对AIGC&#xff08;内容人工智能自动生成&#xff09;领域的热情&#xf…...

安卓逆向_5 --- jeb 和 AndroidStudio 动态调试 smali

Jeb 工具的使用 &#xff1a;https://www.52pojie.cn/forum.php?modviewthread&tid742250&#xff1a;https://zhuanlan.zhihu.com/p/302856081动态调试 smali 有两种方法&#xff1a; Jeb 调试AndroidStudio smalidea 插件动态调试。1、Jeb 动态调试 smali ​JEB是一个…...

docker-容器命令

1.新建启动 docker run options image command [arg..] options: --name"容器新名字" -d&#xff1a;后台运行程序 -it&#xff1a;交互式运行 -P: 随机端口 -p: 指定端口 docker run -it ubuntu /bin/bash docker run -it ubuntu:v1 /bin/bash docker run -it 1c352…...

Spring——是什么?作用?内容?用到的设计模式?

目录 什么是spring&#xff1f; spring是为了解决什么问题而衍生的&#xff1f;&#xff08;历史&#xff09;Spring解决了实际生产中的什么问题&#xff1f; spring包含了哪些部分&#xff1f;&#xff08;组成&#xff09; Spring的特点是什么&#xff1f; spring框架中…...

Qt交叉编译环境搭建

环境及版本&#xff1a; 编译机&#xff1a;Deepin 20.3 Qt 5.12.9 arm编译工具&#xff1a; gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-gnueabihf.tar.xz 运行机&#xff1a;创龙335X开发板 1.下载arm编译工具&#xff1a; gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-…...

Java switch case 语句

Java 的 switch case 语句是一种常用的控制流语句&#xff0c;用于基于不同的输入值执行不同的操作。本文将详细介绍 Java switch case 语句的作用、用法以及在实际工作中的应用。 一、switch case 语句的作用 switch case 语句是一种多分支条件语句&#xff0c;它基于不同的输…...

Linux下MQTT客户端消息订阅与发布实现

MQTT(消息队列遥测传输)是一个基于客户端-服务器的消息发布/订阅传输协议。它基于TCP协议&#xff0c;默认端口号为1883&#xff0c;为此&#xff0c;它也需要一个消息中间件 。MQTT协议是轻量、简单、开放和易于实现的&#xff0c;这些特点使它适用范围非常广泛。在很多情况下…...

代码规范----编程规约(下)

目录 四、OOP规约 五、日期时间 六、集合处理 四、OOP规约 &#xff08;1&#xff09;、避免通过一个类的对象引用访问此类的静态变量或静态方法&#xff0c;无谓增加编译器解析成本&#xff0c;直接用类名来访问即可 &#xff08;2&#xff09;、所有的覆写方法&#xff0…...

c++连接mysql

开始想用mysql connector/c8.0 来操作数据库cmake加上配置后一直编译错误 我这里也没有截屏编译错误大概意思是driver.h里面声明的一个check_lib函数里面用了一个未定义的check找遍了资料都没有找到解决办法最后还是用了原始API如果有人有解决办法请留个位置先上在用的cmake配置…...

CentOS7操作系统安装nginx实战(多种方法,超详细)

文章目录前言一. 实验环境二. 使用yum安装nginx2.1 添加yum源2.1.1 使用官网提供的源地址&#xff08;方法一&#xff09;2.1.2 使用epel的方式进行安装&#xff08;方法二&#xff09;2.2 开始安装nginx2.3 启动并进行测试2.4 其他的一些用法&#xff1a;三. 编译方式安装ngin…...