【C++初阶】C++内存分配与动态内存管理

news2024/10/6 0:33:34

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++初阶
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • C++内存分配与动态内存管理
  • 1.C/C++内存分布
  • 2.C语言中动态内存管理方式
  • 3.C++中动态内存管理方式
    • 3.1 new和delete操作内置类型
    • 3.2 new和delete操作自定义类型
  • 4. operator new和operator delete函数
  • 5. new和delete的实现原理
    • 5.1 内置类型
    • 5.2 自定义类型
  • 6. 定位new和表达式(placement-new)
  • 7. 常见面试题
    • 7.1 malloc/free和new/delete的区别?
    • 7.2 内存泄漏
      • 7.2.1 什么是内存泄漏,内存泄漏的危害?
      • 7.2.2 内存泄漏分类?
      • 7.2.3 内存泄漏解决方案
  • 8.总结:

C++内存分配与动态内存管理

1.C/C++内存分布

我们先来通过以下例题来检验自己是否还清晰的记得C语言内存分配的知识

#include<stdio.h>
#include<stdlib.h>
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";
	const 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);
}

选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1在哪里?____

char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____

填空题:
sizeof(num1) = ____; sizeof(char2) = ____;
strlen(char2) = ____; sizeof(pChar3) = ____;
strlen(pChar3) = ____;sizeof(ptr1) = ____;

答案
从左至右,从上至下给出:
CCCAA  AAADAB  40 5 4 4/8 4 4/8

部分解析
char2是含有5个字节的数组,在栈上
*char2(解引用数组名)是首元素的地址,还是在栈上
pChar3是指针变量,在栈上;虽然用const修饰,为常变量,但本质还是变量
*pChar3解引用找到字符串"abcd\0",在常量区
ptr1是指针变量,在栈上
*ptr1解引用找到malloc开辟的内容,在堆上

sizeof(num1)=4*10,与数组初始化有关
sizeof(char2) =5 strlen(char2) =4,strlen不算\0
sizeof(pChar3) =4(32)/8(64),指针变量

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

为什么说栈是向下增长的,而堆是向上增长的?
 简单来说,在一般情况下,在栈区开辟空间,先开辟的空间地址较高,而在堆区开辟空间,先开辟的空间地址较低。

例如,下面代码中,变量a和变量b存储在栈区,指针c和指针d指向堆区的内存空间:

#include <iostream>
using namespace std;
int main()
{
	//栈区开辟空间,先开辟的空间地址高
	int a = 10;
	int b = 20;
	cout << &a << endl;
	cout << &b << endl;

	//堆区开辟空间,先开辟的空间地址低
	int* c = (int*)malloc(sizeof(int)* 10);
	int* d = (int*)malloc(sizeof(int)* 10);
	cout << c << endl;
	cout << d << endl;
	return 0;
}

因为在栈区开辟空间,先开辟的空间地址较高,所以打印出来a的地址大于b的地址;在堆区开辟空间,先开辟的空间地址较低,所以c指向的空间地址小于d指向的空间地址。

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

2.C语言中动态内存管理方式

1.malloc
 malloc函数的功能是开辟指定字节大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个NULL。传参时只需传入需要开辟的字节个数。

2.calloc
 calloc函数的功能也是开辟指定大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个NULL。calloc函数传参时需要传入开辟的内存用于存放的元素个数和每个元素的大小。calloc函数开辟好内存后会将空间内容中的每一个字节都初始化为0。

3.realloc
 realloc函数可以调整已经开辟好的动态内存的大小,第一个参数是需要调整大小的动态内存的首地址,第二个参数是动态内存调整后的新大小。realloc函数与上面两个函数一样,如果开辟成功便返回开辟好的内存的首地址,开辟失败则返回NULL。

realloc函数调整动态内存大小的时候会有三种情况:
 1、原地扩。需扩展的空间后方有足够的空间可供扩展,此时,realloc函数直接在原空间后方进行扩展,并返回该内存空间首地址(即原来的首地址)。
 2、异地扩。需扩展的空间后方没有足够的空间可供扩展,此时,realloc函数会在堆区中重新找一块满足要求的内存空间,把原空间内的数据拷贝到新空间中,并主动将原空间内存释放(即还给操作系统),返回新内存空间的首地址。
 3、扩充失败。需扩展的空间后方没有足够的空间可供扩展,并且堆区中也没有符合需要开辟的内存大小的空间。结果就是开辟内存失败,返回一个NULL。

4.free
 free函数的作用就是将malloc、calloc以及realloc函数申请的动态内存空间释放,其释放空间的大小取决于之前申请的内存空间的大小。

若还想进一步了解malloc、calloc、realloc和free,具体讲解可以参考我之前的文章C语言动态内存管理

3.C++中动态内存管理方式

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

3.1 new和delete操作内置类型

1.动态申请单个某类型的空间

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

等价于:

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

2.动态申请多个某类型的空间

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

等价于:

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

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

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

等价于:

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

4.动态申请多个某类型的空间并初始化;C++11支持

	//动态申请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[ ]。

3.2 new和delete操作自定义类型

对于以下自定义类型:

class Test
{
public:
	Test() //构造函数
		:_a(0)
	{
		cout << "构造函数" << endl;
	}
	~Test() //析构函数
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};

1.动态申请单个类的空间
用new和delete操作符:

	Test* p1 = new Test; //申请
	delete p1; //销毁

用malloc和free函数:

	Test* p2 = (Test*)malloc(sizeof(Test)); //申请
	free(p2); //销毁

2.动态申请多个类的空间
用new和delete操作符:

	Test* p3 = new Test[10]; //申请
	delete[] p3; //销毁

用malloc和free函数:

	Test* p4 = (Test*)malloc(sizeof(Test)* 10); //申请
	free(p4); //销毁

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

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

4. operator new和operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new和delete在底层是通过调用全局函数operator new和operator delete来申请和释放空间的。
 operator new和operator delete的用法和malloc和free的用法完全一样,其功能都是在堆上申请和释放空间。

	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 new和operator delete是系统提供的全局函数,但是我们也可以针对某个类,重载其专属的operator new和operator delete函数,进而提高效率。

5. new和delete的实现原理

5.1 内置类型

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

5.2 自定义类型

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个对象空间的释放。

6. 定位new和表达式(placement-new)

定位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 a = 0) //构造函数 
		:_a(a)
	{}

	~A() //析构函数
	{}
private:
	int _a;
};
int main()
{
	//new(place_address)type 形式
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;

	//new(place_address)type(initializer-list) 形式
	A* p2 = (A*)malloc(sizeof(A));
	new(p2)A(2021);

	//析构函数也可以显示调用
	p1->~A();
	p2->~A();
	return 0;
}

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

7. 常见面试题

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

7.2 内存泄漏

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

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

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

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;
}

7.2.2 内存泄漏分类?

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

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

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

7.2.3 内存泄漏解决方案

内存泄漏解决方案分为两种:
 1、事前预防型。如智能指针等。
 2、事后查错型。如泄漏检测工具。

8.总结:

今天我们回顾了C语言中内存分配与动态内存管理的知识,认识并具体学习了C++内存分配与动态内存管理的知识。接下来,我们将进行C++中模板初阶与泛型编程的学习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

端口扫描的CS木马样本的分析

序言 病毒、木马是黑客实施网络攻击的常用兵器&#xff0c;有些木马、病毒可以通过免杀技术的加持躲过主流杀毒软件的查杀&#xff0c;从而实现在受害者机器上长期驻留并传播。 CobaltStrike基础 Cobalt Strike简称CS&#xff0c;它是一款非常好用的渗透测试工具&#xff0c…

网络编程--select实现IO复用

何为复用 简单来说&#xff0c;复用就是在1个通信频道中传递多个数据的技术。 常见的复用方式有时分复用和频分复用。 时分复用&#xff1a;即在某一时间段内容&#xff0c;只允许传输一个数据。 频分复用&#xff1a;指的是在某一时间段可以传输多个“频率”不同的数据。 …

朱金宝:数据治理产品发展趋势及Datablau产品最新动态

在刚刚结束的2023数据治理新实践峰会上&#xff0c;Datablau数语科技联合创始人&CTO朱金宝先生分享了对数据治理产品发展趋势的深度思考及Datablau新产品预览&#xff0c;并在现场发布了两款最新工具。 以下是朱金宝先生的演讲实录&#xff0c;为了方便阅读&#xff0c;小…

商家中心之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

字符串String

目录 String 字符串比较 charAt 取数组中的值 案例 substring 案例&#xff1a;手机号加密​ StringBuilder StringBuilder提高效率原理图 StringJoiner 字符串拼接底层方法 String 创造方法 直接赋值通过new关键字 / 构造方法 字符串比较 equals &#xff1a;要求比…

chatgpt赋能Python-pycharm滚轮调大小

PyCharm使用技巧&#xff1a;滚轮调整代码大小提高工作效率 介绍 PyCharm是目前最受欢迎的Python开发IDE之一。然而&#xff0c;即使在使用PyCharm多年的开发者中&#xff0c;很多人都不知道如何使用滚轮来调整代码显示的大小。这在工作中可能会导致眼睛疲劳&#xff0c;降低…

Mysql案例

Mysql案例 1.分组查询排名优先的数据1.1 分组获取最新一条记录1.2 分组获取最新的两条数据 1.分组查询排名优先的数据 -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS user; CREATE TABLE user (id int(…

力扣82删除排序链表中的重复元素 II:思路分析+代码实现+方法总结(三指针法快慢指针法【双指针】递归法)

文章目录 第一部分&#xff1a;题目描述第二部分&#xff1a;代码实现2.1 三指针法2.2 快慢指针法&#xff08;双指针&#xff09;2.3 递归 第一部分&#xff1a;题目描述 &#x1f3e0; 链接&#xff1a;82. 删除排序链表中的重复元素 II - 力扣&#xff08;LeetCode&#xf…

简单快速的在openEuler22.03上部署openGauss2.10

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装openEuler1.创建虚拟机2.安装openEuler系统 二、安装openGauss1.设置openGauss2.创建数据库用户并设置权限3.设置防火墙4.远程连接openGauss 总结 前言…

PyQt5桌面应用开发(15):界面动画

本文目录 PyQt5桌面应用系列界面动画PyQt5的动画框架QPropertyAnimationQAnimationGrouppyqtProperty与插值 一个例子代码代码解析 结论 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开发&#xff08;2&#xff09;&#…

【手机建站】安卓Termux+cpolar内网穿透,搭建外网可以访问的网站

文章目录 概述1.搭建apache2.安装cpolar内网穿透3.公网访问配置4.固定公网地址5.添加站点 概述 Termux是一个Android终端仿真应用程序&#xff0c;用于在 Android 手机上搭建一个完整的Linux 环境&#xff0c;能够实现Linux下的许多基本操作&#xff0c;不需要root权限Termux就…

电脑格式化后数据恢复软件EasyRecovery16

EasyRecovery是一款由Kroll Ontrack公司开发的专业数据恢复软件&#xff0c;旨在帮助用户从各种数据丢失情况下恢复文件。无论是因为误删除、格式化、分区丢失、系统崩溃还是其他原因导致的数据丢失&#xff0c;EasyRecovery都具有强大的恢复功能。 EasyRecovery提供了多种恢复…

什么是 Git 的 cherry-pick?

官方解析 Git 的 cherry-pick 是一种将指定的提交&#xff08;commit&#xff09;应用到当前分支的操作。它可以帮助我们将某个分支上的某次提交复制到另一个分支上&#xff0c;而无需将整个分支合并过来。 通常&#xff0c;我们在使用 Git 进行版本控制时&#xff0c;会在不…

JAVA-Activiti 7与达梦、人大金仓兼容-修改源码包(1)

目录 第一步,下载源码包 第二步,修改源码内容 1.1进行部分源码包修改 1.1.1 在org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl&#xff0c;增加成员变量。 1.1.2 修改org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl 类的getDefaultDatabase…

UML类图画法及其关系

UML类图画法及其关系 本文主要是介绍 UML类图画法及其关系&#xff0c;方便今后温习&#xff01;&#xff01;&#xff01; 一、类之间的关系汇总 泛化&#xff08;Generalization&#xff09;实现&#xff08;Realization&#xff09;关联&#xff08;Association&#xff…

基于springboot+jsp的校园音乐网站系统

该系统能完成的主要功能分为管理员和用户两个用户角色。主要功能包括主页、个人中心、用户管理、校园歌手管理、明星歌手管理、歌曲类别管理、校园歌曲管理、歌曲MV管理、歌手歌曲管理、系统管理等。而用户登入系统也可以对自己的信息以及修改个人资料进行查看等功能。该系统在…

YOLO-NAS C++部署 2023.5.17

这不最近新出了网络&#xff0c;YOLO-NAS&#xff0c;听过性能和速度都不错&#xff0c;而且int8量化后效果也不错。 一、吐槽 但是我打开该项目阅读readme.txt时候&#xff0c;发现这些示例网站一个都打不开&#xff01; 而且readme.txt很不详细&#xff0c;你想训练自己的模…

设计模式之【访问者模式】,动态双分派的魅力

文章目录 一、什么是访问者模式1、访问者模式的应用场景2、访问者模式的五大角色3、访问者模式的优缺点 二、实例1、访问者模式的通用写法2、宠物喂食实例3、KPI考核案例小总结 三、分派1、什么是分派2、静态分派3、动态分派4、双分派5、访问者模式中的伪动态双分派 四、访问者…

mkv转mp4格式怎么转,5种便捷工具盘点

mkv转mp4格式怎么转&#xff1f;因为当我们下载视频时&#xff0c;通常无法选择格式&#xff0c;这可能会导致下载的视频无法打开。如果下载的是MKV格式&#xff0c;它可以容纳多个音频、视频和字幕流。然而&#xff0c;并非所有播放器都支持MKV格式的视频文件。尽管MKV是常见的…

2D车道线检测算法总结

关于2D车道线检测算法的总结主要分为两类&#xff1a;一类基于语义分割来做&#xff0c;一类基于anchor和关键点来做。还有基于曲线方程来做的&#xff0c;但是落地的话还是上面两种为主。 一、基于语义分割的车道线检测算法 1.LaneNet 论文创新点&#xff1a; 1.将车道线检…