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

【C++】内存管理与分配

目录

💕1.内存分配 

 💕2.动态开辟与释放内存(关键字new,delete)

 💕3.动态开辟时,类的问题 

💕4.动态开辟内存的写法

 💕5.operator new[]与operator delete[]函数

💕6.new和delete的实现原理

💕7.delete与free混合使用的问题

💕8.关于new/delete时,显示调用构造/析构函数的问题

💕9.开辟失败需要单独写吗?

💕10.完结撒花 


 (最新更新时间——2025.1.18)

💕1.内存分配 

我们先来看一下内存的分配->:

在C++中,内存分配就如上图所示,但光看没有用,我们做几道题

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;
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);
}

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

答案如下->:

左边五个没什么可以讲的,主要讲右边六个,char2是数组的名字,数组名表示首元素地址,所以解引用数组名其实指的是"abcd"中的'a',虽然字符串"abcd"存在常量区,但因为是数组,所以字符串被拷贝到栈里了,所以*char是栈区。


pChar3是一个指针,所以存放在栈区,*pChar3所指向的内容在常量区,所以*pChar3在常量区。


ptr1是一个指针,在栈区,*ptr所指向的内容在堆区,所以*ptr在堆区


 💕2.动态开辟与释放内存(关键字new,delete)

 在C++中,我们有了新的动态内存开辟方式,那就是new,那我们之前学的malloc等还可以用吗,答案是可以的,但是会有一些问题,什么问题?我们后面讲

不懂动态内存分配的请看这篇文章->:

C语言——动态内存分配


new关键字的使用

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;

int main()
{
	int* a = new int;//开辟一个int的空间
	int* b = new int[4];//开辟4个int的空间
}

如何?是不是比malloc很方便

那我们该如何释放呢?

这时候就要用到关键字delete,代码如下->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;

int main()
{
	int* a = new int;//开辟一个int的空间
	int* b = new int[4];//开辟4个int的空间

	delete a;//释放单个空间
	delete[] b;//释放多个空间
}


我们之前学过,malloc开辟空间的同时不可以进行初始化,但是calloc可以

那new可以开辟空间的同时进行初始化吗?

答案是可以的,代码如下->:
 

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;

int main()
{
	int* a = new int(10);//单个初始化
	int* b = new int[4] {1, 2, 3, 4};//多个初始化

	delete a;//释放单个空间
	delete[] b;//释放多个空间
}

 💕3.动态开辟时,类的问题 

我们想给类开辟空间时该怎么做,我们先用malloc为例

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;
class Date
{
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date* d1 = (Date*)malloc(sizeof(Date));//很正常的开辟空间
	d1->_year;//不能访问,因为是私有
}

使用malloc开辟类的空间会导致无法访问类内部成员,因为类内部成员基本都是私有的


但如果用new呢?

看这些代码,我们可以得知,new开辟类的空间时,是会调用类的构造函数的,这就导致我们可以对类中的成员进行初始化


💕4.动态开辟内存的写法

接下来请看整合起来的代码,更加易懂些->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int a = 10, int b = 20)
		:_year(a)
		,_month(b)
		,_day(a)
	{
		cout << "第"<<count<<"次调用构造函数" << endl;
		count++;
	}
	~Date()
	{
		cout << "第" << _count << "次调用析构函数" << endl;
		_count++;
	}

private:
	int _year;
	int _month;
	int _day;
	static int count;
	static int _count;
};
int Date::count = 0;
int Date::_count = 0;
int main()
{
	int* a = new int;//开辟1个int
	int* b = new int[10];//开辟10个int

	int* c = new int(2);//开辟1个int并初始化为2
	int* d = new int[5] {1, 2, 3, 4, 5};//开辟5个int并依次初始化为1,2,3,4,5

	Date* d1 = new Date;//开辟1个Date,并调用它的构造函数
	Date* d2 = new Date[5];//开辟5个Date,并分别调用它们的构造函数

	Date* d3 = new Date(2);//开辟1个Date,并调用它的构造函数,并将2传给构造函数
	Date* d4 = new Date[5]{ 9,8,{1,2},{3,4},{5,6} };//开辟5个Date,并调用它们的构造函数
	//第一个Date将9传给构造函数
	//第二个Date将8传给构造函数
	//第三个Date将1,2传给构造函数
	//第四个Date将3,4传给构造函数
	//第五个Date将5,6传给构造函数

	delete a;
	delete[] b;
	delete c;
	delete[] d;
	delete d1;
	delete[] d2;
	delete d3;
	delete[] d4;
}

我们总结一下->:

new 【自定义类型】除了开空间,还会调用构造函数
delete 对于【自定义类型】会调用析构函数


 💕5.operator new[]与operator delete[]函数

我们在开辟多个类型数据时用new T[N],其内部调用的是operator new[ ]函数,而operator new[ ]函数其实就是由operator new函数实现完成的,见下面汇编代码:

同理delete[ ]函数内部实现是由operator delete[ ]函数实现的,而operator delete[ ]函数内部实现也是由operator delete函数所实现的:


💕6.new和delete的实现原理

1.这里以下图来看,我们在new开辟空间时,是先调用operator new(malloc)函数来申请空间的,然后在这块申请空间上调用该对象的构造函数,开辟新的空间并来完成对象的构造

2.在delete时,先执行对象的析构函数,将构造函数开出的空间里的资源先清理掉,然后调用operator delete(free)函数来释放operator new(malloc)申请的空间

3.new T[ ]开辟多个空间时,只会调用一次operator new(malloc)函数,每次申请新的空间时,每个对象都会调用一次它的构造函数

4.在delete[ ]时也只会调用一次operator delete(free)函数,调用N次析构函数


💕7.delete与free混合使用的问题

如下图,我们可以看到类的大小为4个字节,开辟一个类的空间大小不应该也是4吗?为什么是8呢?

这其实是因为在开辟类的空间时,会单独开辟出一个整型的空间,用来计数开辟了几个类的空间,我们再开辟一个来试试

这次,我们开辟了两个类的空间大小,但是开辟了12个字节,这也证实了我们的说法

其实单独开辟一个整形的空间用来存储个数,是为了delete[ ]时知道有多少个对象,要调用多少次析构函数


那如果我们分别用delete和free释放时,它会从哪里释放呢?

我们来试一试

用delete时,所有的开辟空间都被释放掉了,那用free呢(如下)?

可见,free只会释放掉new时所调用的operator new所开辟出来的空间,对于单独开辟出的那一个用于计数的int类型的空间并不会释放


💕8.关于new/delete时,显示调用构造/析构函数的问题

 在new/delete时,是不可以直接显示调用构造函数的,我把写法放在下面的代码里了

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int a = 10, int b = 20)
		:_year(a)
		, _month(b)
		, _day(a)
	{

	}
	~Date()
	{

	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date* d1 = new Date;//开辟1个Date,并调用它的构造函数

	d1->Date();//不支持这样显示调用构造函数
	new (d1)Date;// 对已有空间,显示调用构造
	new (d1)Date(10);// 对已有空间,显示调用构造


	Date* d2 = new Date[10];

	new (d2)Date[10]{ 1,2,3,4,5,6,7 };// 对已有空间,显示调用构造
	//也可以这么写
	for (int i = 0; i < 10; i++)
	{
		new (d2+i)Date(i);
	}

	// 释放时的写法
	for (int i = 0; i < 10; i++)
	{
		(d2 + i)->~Date();
	}
}

💕9.开辟失败需要单独写吗?

在C++中,使用new开辟空间可以每次不写判断是否开辟成功,在我们学习 "异常" 后就会懂,这里先不做讲解

💕10.完结撒花 


http://www.kler.cn/a/511596.html

相关文章:

  • 国内汽车法规政策标准解读:GB 44495-2024《汽车整车信息安全技术要求》
  • 语音技术在播客领域的应用(2)
  • 风吹字符起,诗意Linux:一场指令与自由的浪漫邂逅(上)
  • 1.写在前面
  • 机器学习中的方差与偏差
  • 【大数据2025】Yarn 总结
  • Leetcode::3427.变长子数组求和
  • vue+高德API搭建前端Echarts图表页面
  • JavaScript笔记基础篇04——对象
  • win内核内部直接irp读取文件写入文件
  • RabbitMQ 进阶
  • Linux内存管理(Linux内存架构,malloc,slab的实现)
  • 排序算法(C语言版)
  • Vue3数据响应式原理
  • PHP变量
  • TiDB 和 MySQL 的关系:这两者到底有什么不同和联系?
  • Linux(NFS服务)
  • SoC芯片架构揭秘:从Arm核心到高速通信
  • angular项目知识点
  • 《重生到现代之从零开始的C++生活》—— 入门基础语法2
  • Qt:自定义tooltip
  • SpringBoot节假日(OneAPI和天聚数行)
  • 【系统分享01】Python+Vue电影推荐系统
  • ASP.NET Core 中基于 Cookie 的身份鉴权实现
  • 什么是HTTP POST请求?初学者指南与示范
  • HackMyVM-Klim靶机的测试报告