<C++> 内存管理

1.C/C++内存分布

让我们先来看看下面这段代码

int globalVar = 1;
static int staticGlobalVar = 1;
void Test() {
    static int staticVar = 1;
    int localVar = 1;
    int num1[10] = {1, 2, 3, 4};
    char char2[] = "abcd";
    char *pChar3 = "abcd";
    int *ptr1 = (int *) malloc(sizeof(int) * 4);
    int *ptr2 = (int *) calloc(4, sizeof(int));
    int *ptr3 = (int *) realloc(ptr2, sizeof(int) * 4);
    free(ptr1);
    free(ptr3);
}

以下是各个部分分别存储在内存中的哪一个区域

在这里插入图片描述

说明:

1、栈又叫堆栈,用于存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2、内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
3、堆用于存储运行时动态内存分配,堆是向上增长的。
4、数据段又叫静态区,用于存储全局数据和静态数据。
5、代码段又叫常量区,用于存放可执行的代码和只读常量。

顺便提一下:为什么说栈是向下增长的,而堆是向上增长的?

简单来说,在一般情况下,在栈区开辟空间,先开辟的空间地址较高,而在堆区开辟空间,先开辟的空间地址较低。

注意:在堆区开辟空间,后开辟的空间地址不一定比先开辟的空间地址高。因为在堆区,后开辟的空间也有可能位于前面某一被释放的空间位置。

2.C++动态内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理

new/delete操作内置类型

一、动态申请单个某类型的空间

//动态申请单个int类型的空间
int* p1 = new int; //申请

delete p1; //销毁

其作用等价于:

//动态申请单个int类型的空间
int* p2 = (int*)malloc(sizeof(int)); //申请

free(p2); //销毁

二、动态申请多个某类型的空间

//动态申请10个int类型的空间
int* p3 = new int[10]; //申请

delete[] p3; //销毁

其作用等价于:

//动态申请10个int类型的空间
int* p4 = (int*)malloc(sizeof(int)* 10); //申请

free(p4); //销毁

三、动态申请单个某类型的空间并初始化

//动态申请单个int类型的空间并初始化为10
int* p5 = new int(10); //申请 + 赋值

delete p5; //销毁

其作用等价于:

//动态申请一个int类型的空间并初始化为10
int* p6 = (int*)malloc(sizeof(int)); //申请
*p6 = 10; //赋值

free(p6); //销毁

四、动态申请多个某类型的空间并初始化

//动态申请10个int类型的空间并初始化为0到9
int* p7 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //申请 + 赋值

delete[] p7; //销毁

其作用等价于:

//动态申请10个int类型的空间并初始化为0到9
int* p8 = (int*)malloc(sizeof(int)* 10); //申请
for (int i = 0; i < 10; i++) //赋值
{
	p8[i] = i;
}

free(p8); //销毁

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[],注意:匹配起来使用。

new/delete操作自定义类型

有以下类:

class A {
public:
    A(int a = 0)
        : _a(a) {
        cout << "A():" << this << endl;
    }
    ~A() {
        cout << "~A():" << this << endl;
    }

private:
    int _a;
};

一、动态申请单个类

A *p1 = new A();//动态申请单个类
delete p1;

//等价于
A *p2 = (A *) malloc(sizeof(A));
free(p2);

二、动态申请多个类

A *p5 = new A[10];//动态申请多个类
delete[] p5;

//等价于
A *p6 = (A *) malloc(sizeof(A) * 10);
free(p6);

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会。

总结一下:

1、C++中如果是申请内置类型的对象或是数组,用new/delete和malloc/free没有什么区别。
2、如果是自定义类型,区别很大,new和delete分别是开空间+构造函数、析构函数+释放空间,而malloc和free仅仅是开空间和释放空间。
3、建议在C++中无论是内置类型还是自定义类型的申请和释放,尽量都使用new和delete。

3.operator new和operator delete函数

newdelete是用户进行动态内存申请和释放的操作符,operator newoperator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。

int* p1 = (int*)operator new(sizeof(int)* 10); //申请
operator delete(p1); //销毁

//等价于
int* p2 = (int*)malloc(sizeof(int)* 10); //申请
free(p2); //销毁

operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

在这里插入图片描述

注意:虽然说operator newoperator delete是系统提供的全局函数,但是我们也可以针对某个类,重载其专属的operator new和operator delete函数,进而提高效率。

4.new和delete的实现原理

内置类型

如果申请的是内置类型的空间,new/delete和malloc/free基本类似,不同的是,new/delete申请释放的是单个元素的空间,new[]/delete[ ]申请释放的是连续的空间,此外,malloc申请失败会返回NULL,而new申请失败会抛异常。

自定义类型

new的原理
 1、调用operator new函数申请空间。
 2、在申请的空间上执行构造函数,完成对象的构造。

delete的原理
 1、在空间上执行析构函数,完成对象中资源的清理工作。
 2、调用operator delete函数释放对象的空间。

new T[N]的原理
 1、调用operator new[ ]函数,在operator new[ ]函数中实际调用operator new函数完成N个对象空间的申请。
 2、在申请的空间上执行N次构造函数。

delete[ ] 的原理
 1、在空间上执行N次析构函数,完成N个对象中资源的清理。
 2、调用operator delete[ ]函数,在operator delete[ ]函数中实际调用operator delete函数完成N个对象空间的释放。

5.定位new表达式

定位new表达式是C++中的一种高级用法,允许你在特定的内存位置上创建对象,而不是在默认的堆内存上分配。这在某些特殊情况下很有用,例如在特定内存区域上实现内存池或在共享内存上创建对象等。

示例:

#include <iostream>

class MyClass {
public:
    MyClass(int value) : data(value) {
        std::cout << "构造函数: " << data << std::endl;
    }

    ~MyClass() {
        std::cout << "析构函数: " << data << std::endl;
    }

private:
    int data;
};

int main() {
    // 分配一块内存
    void* memory = operator new(sizeof(MyClass));

    // 在预分配的内存上使用定位new表达式创建对象
    MyClass* obj = new (memory) MyClass(42);

    // 手动调用析构函数
    obj->~MyClass();

    // 释放预分配的内存
    operator delete(memory);

    return 0;
}

注意:在未使用定位new表达式进行显示调用构造函数进行初始化之前,operator new申请的空间还不能算是一个对象,它只不过是与A对象大小相同的一块空间,因为构造函数还没有执行。

6.常见问题

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在释放空间前会调用析构函数完成 空间中资源的清理

什么是内存泄漏,内存泄漏的危害?

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

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

void MemoryLeaks() {
    // 1.内存申请了忘记释放
    int *p1 = (int *) malloc(sizeof(int));
    int *p2 = new int;

    // 2.异常安全问题
    int *p3 = new int[10];

    Func();// 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.

    delete[] p3;
}

内存泄漏分类

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

  • 堆内存泄漏(Heap leak)

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

  • 系统资源泄漏

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

如何避免内存泄漏?

1、工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记住匹配的去释放。
2、采用RAII思想或者智能指针来管理资源。
3、有些公司内部规范使用内部实现的私有内存管理库,该库自带内存泄漏检测的功能选项。
4、出问题了使用内存泄漏工具检测。

delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak

  • 系统资源泄漏

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

如何避免内存泄漏?

1、工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记住匹配的去释放。
2、采用RAII思想或者智能指针来管理资源。
3、有些公司内部规范使用内部实现的私有内存管理库,该库自带内存泄漏检测的功能选项。
4、出问题了使用内存泄漏工具检测。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/90838.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Git】学习总结

【Git】学习总结 【一】安装【二】Git克隆项目代码【1】idea下载git项目【2】创建新的分支【3】新建的分支推送到远程【4】合并最新代码到主分支【5】切换分支 【三】提交本地项目到远程&#x1f680;1. 配置 Git&#x1f680;2. 创建项目远程仓库&#x1f680;3. 初始化本地仓…

vue2 生命周期,工程化开发入门

一、今日目标 1.生命周期 生命周期介绍生命周期的四个阶段生命周期钩子声明周期案例 2.工程化开发入门 工程化开发和脚手架项目运行流程组件化组件注册 二、Vue生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09;什么…

Hightopo 使用心得(6)- 3D场景环境配置(天空球,雾化,辉光,景深)

在前一篇文章《Hightopo 使用心得&#xff08;5&#xff09;- 动画的实现》中&#xff0c;我们将一个直升机模型放到了3D场景中。同时&#xff0c;还利用动画实现了让该直升机围绕山体巡逻。在这篇文章中&#xff0c;我们将对上一篇的场景进行一些环境上的丰富与美化。让场景更…

docker搭建opengrok环境2

引言&#xff1a; 虚拟机关闭后重新开启&#xff0c;理论上是需要重新启动一下docker的&#xff0c;以重新启动其中的服务。 命令基础&#xff1a; docker images&#xff1a;查看docker中现有的镜像 docker container ls -all&#xff1a;查看docker中目前在运行的containe…

2023国赛数学建模D题思路模型代码 高教社杯

本次比赛我们将会全程更新思路模型及代码&#xff0c;大家查看文末名片获取 之前国赛相关的资料和助攻可以查看 2022数学建模国赛C题思路分析_2022国赛c题matlab_UST数模社_的博客-CSDN博客 2022国赛数学建模A题B题C题D题资料思路汇总 高教社杯_2022国赛c题matlab_UST数模社…

【Unity】制作一个简单的菜单栏页面并实现其功能

这是一个简单的菜单页面制作&#xff0c;接下来我们将制作一个完整的菜单页面&#xff0c;并且通过一定的代码去实现它对应的效果。这个主要的功能就是我们在游戏中如果想暂停一下或者重新开始&#xff0c;那么就要用到我们这个功能。接下来我们将实现在游戏中按ESC退出键可以调…

聚观早报 | 青瓷游戏上半年营收3.34亿元;如祺出行冲击IPO

【聚观365】8月26日消息 青瓷游戏上半年营收3.34亿元 如祺出行冲击IPO 索尼互动娱乐将收购Audeze 昆仑万维上半年净利润3.6亿元 T-Mobile计划在未来五周内裁员5000人 青瓷游戏上半年营收3.34亿元 青瓷游戏发布截至2023年6月30日止的中期业绩&#xff0c;财报显示&#xf…

vue3范围选择组件封装

个人项目地址&#xff1a; SubTopH前端开发个人站 &#xff08;自己开发的前端功能和UI组件&#xff0c;一些有趣的小功能&#xff0c;感兴趣的伙伴可以访问&#xff0c;欢迎提出更好的想法&#xff0c;私信沟通&#xff0c;网站属于静态页面&#xff09; SubTopH前端开发个人站…

K8S如何部署ZooKeeper以及如何进行ZooKeeper的平滑替换

前言 在之前的章节中&#xff0c;我们已经成功地将Dubbo项目迁移到了云环境。在这个过程中&#xff0c;我们选择了单机ZooKeeper作为注册中心。接下来&#xff0c;我们将探讨如何将单机ZooKeeper部署到云端&#xff0c;以及在上云过程中可能遇到的问题及解决方案。 ZooKeeper…

免费开源使用的几款红黑网络流量工具,自动化的多功能网络侦查工具、超级关键词URL采集工具、Burpsuite被动扫描流量转发插件

免费开源使用的几款红黑网络流量工具&#xff0c;自动化的多功能网络侦查工具、超级关键词URL采集工具、Burpsuite被动扫描流量转发插件。 #################### 免责声明&#xff1a;工具本身并无好坏&#xff0c;希望大家以遵守《网络安全法》相关法律为前提来使用该工具&am…

【C++】初步认识模板

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、泛型编程二、函数模板2.1 函…

【Java】基础练习(十)

1.判断邮箱 输入一个电子邮箱&#xff0c;判断是否是正确电子邮箱地址。 正确的邮箱地址&#xff1a; 必须包含 字符&#xff0c;不能是开头或结尾必须以 .com结尾和.com之间必须有其他字符 (1) Email类&#xff1a; package swp.kaifamiao.codes.Java.d0823; /** 输入一个…

4-4 Representing text Exercise

本文所用资料下载 一. Representing text Let’s load Jane Austen’s Pride and Prejudice. We first split our text into a list of lines and pick an arbitrary line to focus on: with open(D:jane-austen/1342-0.txt, encodingutf8) as f:text f.read() lines text.…

简单了解文件上传漏洞(md版)

简单了解文件上传漏洞 一、什么是文件上传漏洞二、常见功能点三、成功的前提四、文件上传的校验方式五、Pass-1六、Pass-2七、蚁剑结合msf获取目标权限 一、什么是文件上传漏洞 在文件上传的功能处&#xff0c;如果服务端未对上传的文件进行严格的验证和过滤&#xff0c;导致攻…

登录校验-Filter-详解

目录 执行流程 拦截路径 过滤器链 小结 执行流程 过滤器Filter拦截到请求之后&#xff0c;首先执行方放行之前的逻辑&#xff0c;然后执行放行操作&#xff08;doFilter&#xff09;&#xff0c;然后会访问对应的Web资源&#xff08;对应的Controller类&#xff09;&#…

【C++】C++ 引用详解 ⑤ ( 函数 “ 引用类型返回值 “ 当左值被赋值 )

文章目录 一、函数返回值不能是 " 局部变量 " 的引用或指针1、函数返回值常用用法2、分析函数 " 普通返回值 " 做左值的情况3、分析函数 " 引用返回值 " 做左值的情况 函数返回值 能作为 左值 , 是很重要的概念 , 这是实现 " 链式编程 &quo…

Acrobat Pro DC软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Acrobat Pro DC是Adobe公司开发的一款PDF编辑软件&#xff0c;简称为DC&#xff0c;是Acrobat系列软件中的一款&#xff0c;是行业内的标准工具&#xff0c;被广泛应用于文档处理、电子合同、PDF表单等领域。 Acrobat Pro DC软…

【SpringBoot】第一篇:redis使用

背景&#xff1a; 本文是教初学者如何正确使用和接入redis。 一、引入依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><depen…

适配器模式实现stack和queue

适配器模式实现stack和queue 什么是适配器模式&#xff1f;STL标准库中stack和queue的底层结构stack的模拟实现queue的模拟实现 什么是适配器模式&#xff1f; 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff…

基于广义神经网络的网络入侵检测Matlab代码

1.案例背景 1.1 FCM 聚类算法 聚类方法是数据挖掘中经常使用的方法,它将物理的或抽象的对象分为几个种群,每个种群内部个体间具有较高的相似性,不同群体内部间个体相似性较低。模糊c均值聚类算法(Fuzzy C- Mean, FCM)是用隶属度确定每个元素属于某个类别程度的一种聚类算法&am…