详解c++---内存管理

news2024/9/24 1:20:31

这里写目录标题

  • c语言在堆上申请空间
    • malloc
    • calloc
    • realloc
    • free
  • c++中向堆中申请空间的形式
    • new的介绍
    • delete的介绍
  • new与自定义类型
  • new与malloc的不同
  • 定位new
  • operator new与operator delete函数

c语言在堆上申请空间

在之前的学习中我们知道c语言主要是通过malloc free calloc,realloc这几个函数在堆上申请和释放的空间,那么这里为了防止大家忘了这几个函数,我们下面给大家看看这几个函数的使用方法。

malloc

在这里插入图片描述
那么这个函数的参数就是你想要开辟的空间的大小,这个函数的返回值就是一个指针,这个指针指向的就是我们这个内存的起始位置,但是这个指针的类型是void*类型,所以我们在使用这个函数的时候我们就得加一个强制类型转换,其次通过上面的介绍我们还知道这里开辟的空间他是不会主动初始化的,所以在我们不主动给这段空间赋值之前这段内存里面存放的是随机值,而且当我们申请一段特别大的空间,或者不停的申请的话,我们这里会出现申请空间失败的情况,所以当空间申请失败的话,这个函数会放回一个空指针,所以在往后我们用这个函数来申请空间的时候,我们就得在申请空间之后判断一些这里的空间是否开辟成功,如果开辟失败的话我们就打印一些文字出来用于警示使用者,那么我们这里就可以通过下面的代码来给那大家演示一下这个函数的使用,比如说我想要在堆上申请一个int大小的空间:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* pa = (int*)malloc(sizeof(int));
	if (pa == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	*pa = 2022;
	printf("该内存上的数据为:%d", *pa);
	 free(pa);
	 pa=NULL;
	return 0;
}

运行结果如下:
在这里插入图片描述
如果我想要申请两个int类型大小的空间,我们就只用在malloc函数中的sizeof后面加个*2即可,那么我们的代码如下:

int main()
{
	int* pa = (int*)malloc(sizeof(int)*2);
	if (pa == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	*pa = 2022;
	*(pa + 1) = 2023;
	printf("该内存上的数据为:%d %d", *pa,*(pa+1));
	free(pa);
	 pa=NULL;
	return 0;
}

其代码的运行结果如下:
在这里插入图片描述

calloc

calloc的函数介绍如下:
在这里插入图片描述
calloc和malloc这两个函数的功能大致是一样的,这两个函数的区别就在calloc的参数不一样,并且calloc在开辟空间的时候会顺带进行初始化,将空间中的内容全部为0,malloc的参数只有一个就是你要开辟空间的大小,而这里的参数有两个其意思就是你想要开辟多少个类型大小的空间,比如说我想要开辟3个大小为int类型的空间,那么这样的话参数就是3,sizeof(int),我想要开辟4个大小为char类型的空间,那么这样的话参数就是:4,sizeof(char)等等,这里的返回值也是和malloc一样的如果开辟成功就返回这里的空间的起始地址,如果开辟失败这里就返回一个空指针,那么我们来看看下面的例子,我要开辟4个大小为int类型的空间,并且不对这些空间做任何的操作,来看看这个空间中的值,那么我们的代码就如下:

int main()
{
	int* pa = (int *)calloc(4, sizeof(int));
	if (pa == NULL)
	{
		printf("calloc fail");
		exit(-1);
	}
	printf("%d ", *pa);
	printf("%d ", *(pa + 1));
	printf("%d ", *(pa + 2));
	printf("%d ", *(pa + 3));
	free(pa);
	pa=NULL;
	return 0;
}

我们将这个代码运行一下就可以看到这里打印出来的结果都是0
在这里插入图片描述

realloc

我们来看看这个函数的基本介绍:
在这里插入图片描述
realloc这个函数的功能就是改变我们之前开辟的空间大小,比如说我之前开辟了一个20字节空间,那么我想把这个空间的扩大成40个字节,那么这里我们就可以使用realloc这个函数来实现扩容,那么这个函数有两个参数,一个参数是原来空间的地址,另外一个参数就是你想要更改的空间大小的值,当然扩容也有成功或者失败,如果我们这里扩容成功了就会返回该空间新的地址当然这个地址可能跟原来一模一样,也可能跟原来不一样,那么这个取决于堆中的情况,既然有成功那么就会有失败,如果是失败的话我们这个函数返回的值就是一个空指针,所以当我们在用这个函数来开辟空间的话我们这里就不要用原来的那个指针来进行接收,因为一旦开辟失败我们那个指针的值就会变成空指针,这就导致了我们的空间不仅开辟失败了,而且原来的那个空间我们还找不到了,这就会导致内存泄漏这种问题的发生,那么我们可以看看下面的这个例子:先用malloc开辟20字节的空间,然后再用realloc将这个空间放大到40个字节,那么这里我们的代码就如下:

int main()
{
	int* p = (int*)malloc(sizeof(int) * 5);
	if (p == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	for (int i = 0; i < 4; i++)
	{
		*(p + i) = i;
	}
	int* pa = (int*)realloc(p, 40);
	if (pa == NULL)
	{
		printf("realloc fail\n");
		exit(-1);
	}
	p = pa;
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}	
	free(p);
	p = NULL;
	pa = NULL;
	return 0;
}

这段代码运行的结果就如下:
在这里插入图片描述
当然我们这里是放大的情况,我们的realloc还可以将空间缩小,那么这里我们就不多说了,大家自己下去尝试一下。

free

在这里插入图片描述
当我们在堆中申请了空间之后,如果有朝一日我们不想使用这个空间之后,我们就可以通过free函数将这个空间释放掉,其形式就是在free函数中的括号里面写进你想要释放的空间的地址。这里就不多讲了因为上面的例子中有,那么以上的几个函数就是我们的c语言中的向堆中申请空间,那我们c++又是如何来向堆中申请空间的呢?我们接着往下看。

c++中向堆中申请空间的形式

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

new的介绍

在c语言中我们是通过函数来向堆中申请,但是在c++当中我们是通过关键字来向空间申请的空间,其基本形式如下:

int main()
{
	int* p = new int;
	return 0;
}

new后面加个你想开辟的空间的类型,比如说整型,字符型等等,然后再用一个对应类型的指针来进行接收就行,那么这就是我们new开辟空间的形式,那么这种形式他是不会对我们开辟的空间进行初始化的,我们可以通过内存来看到这里p所指向的内容是一个随机值:
在这里插入图片描述
所以当我们用这种形式来进行初始化的话是不会对开辟出来的空间进行初始化的,所以如果你想要对其进行初始化的话我们就可以在new的类型后面加上一个括号,在括号里面输入你想要初始化的值,比如说这样:

int main()
{
	int* p = new int(10);
	return 0;
}

这样的话我们再来看看内存中的值就可以发现堆上对应的那个空间就被初始化成为了10:
在这里插入图片描述
当然在我们实际使用的过程中我们不仅仅只创建一个整型的空间,有时候我们还要创建一个数组,这个数组中含有100个整型,那么面对这种情况我们就得在类型的后面加上一个方括号,在方括号里填入你想要开辟类型大小的个数,比如说我想要开辟10个整型大小的空间,那么我们这里就得在方括号里填入10,比如说我们下面的代码:

int main()
{
	int* p = new int [10];
	return 0;
}

在这里插入图片描述
当然对于这种形式我们的编译器也是不会对这个内存中的数据做任何的初始化,如果你想要对这个内存中的数据进行初始化的话,我们就得在后面加个花括号,在花括号里面填入你想要初始化成什么样的数据,比如说我们下面的代码:

int main()
{
	int* p = new int[10]{0,1,2,3,4,5,6,7,8,9};
	return 0;
}

在这里插入图片描述
那么以上就是我们new关键字的介绍希望大家可以理解。

delete的介绍

在c语言当中我们是使用free函数来释放这里申请的空间,那么在c++当中我们是使用关键字delete来释放我们这里申请的空间,那么这里的形式就是delet后面加上空间的起始地址就就可以了,比如说我们上面的代码要释放空间的形式就如下,对于单个数据就是这样的:

int main()
{
	int* p = new int;
	delete p;
	return 0;
}

对于多个数据,我们这里的形式就得做出一些改变:在地址的前面加上一个方括号,这样就表示这里是一个连续的数据,比如说我们上面申请的数组,在释放的时候就是这样的:

int main()
{
	int* p = new int [10];
	delete[] p;
	return 0;
}

那么这里大家要注意的一点就是我们这里在释放的时候一定要做到匹配,当我们只申请了一个空间的时候我们在释放的时候就不要加上方括号,如果我们这里申请了一连续的空间的时候我们这里就必须得加方括号。如果不匹配的话我们这里就有可能报错。

new与自定义类型

对于内置内省c++的初始化方式和c语言的初始化方式没有任何的区别,但是对于自定义类型两者就会有些不同,比如说我们下面自己写了一个类:

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

我们用malloc在堆上实例化出来一个对象时,我们可以看到它是不会调用这个对象中的构造函数的:

int main()
{
	A* PA = (A*)malloc(sizeof(A));
	if (PA == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	free(PA);
	return 0;
}

在这里插入图片描述
但是我们用new来实例化对象的话,我们可以看到它是会调用这个类中的构造函数和析构函数的,比如说我们下面的代码。

int main()
{
	A* PA = (A*)new A;
	delete PA;
	return 0;
}

在这里插入图片描述

new与malloc的不同

在申请或者扩容空间的时候,malloc和realloc和celloc如果申请或者扩容失败就会返回一个空指针,比如说我们下面的代码:

int main()
{
	while (1)
	{
		int* p = (int*)malloc(sizeof(int) * 1024*1024);
		if (p == NULL)
		{
			printf("malloc fail\n");
			break;
		}
		printf("success malloc\n");
	}
	return 0;
}

那么这段代码运行了一小会之后它就会打印出来malloc fail这就话,那么这就说明malloc在申请空间失败的时候确实会返回一个空指针
在这里插入图片描述
但是new却不会,当他申请空间失败的时候他会返回抛异常而不是返回一个空指针,比如说我们下面的代码:

int main()
{
	while (1)
	{
		int* p = new int[sizeof(int) * 1024 * 1024];
		if (p == NULL)
		{
			printf("malloc fail\n");
			break;
		}
		printf("success malloc\n");
	}
	return 0;
}

我们这里将malloc改成了new,同样放到一个循环里面,但是这里我们却发现在执行的过程中直到编译结束了也没有打印出来malloc fail这句话,那么这就说明new在开辟空间失败的时候是不会返回空指针的,而是会抛异常

在这里插入图片描述
我们再来看看下面的代码:

void Test()
{
	while (1)
	{
		// new失败 抛异常 -- 不需要检查返回值
		char* p1 = new char[1024 * 1024 * 1024];
		cout << (void*)p1 << endl;
	}
}

int main()
{
	try
	{
		Test();	
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

我们将这个代码运行一下就可以看到这里申请空间失败了,但是屏幕上也打印出来该错误的原因,那么这就是两者的不同:new失败 抛异常 – 不需要检查返回值。
在这里插入图片描述

定位new

我们大家都知道类中的构造函数一般都是编译器自己调用的,但是类中的析构函数却可以我们人为的手动调用,那么这里就有个问题我们能不能人为的手动的调用构造函数呢?答案是可以的,因为我们上面讲过用malloc来创建一个类的时候他是不会主动的调用构造函数和析构函数的,所以我们这里就可以使用定位new表达式在已分配的原始内存空间中调用构造函数初始化一个对象。其初始化的格式为:new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表,比如说我们下面的代码:

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
// 定位new
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A();
	free(p1);
	return 0;
}

那么这就是定位new的使用格式。
定位new的使用场景为:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。所以在实际的使用过程中我们是可以直接通过operator new和operator delete来申请和释放空间的。我们来看看这两个函数的底层的实现:


void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
	void *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);
}

大家仔细观察一下这个operator new的底层实现,大家其实可以看到这里还是通过mallloc来申请的空间,我们再来看看operator delete的底层实现:

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)

我们可以看到其实operator delete其实也是通过free来实现的功能,那么这就说明我们平时使用的new他只不过是一个封装,当我们在调用他的时候他会干两件事情一个是调用函数operator new另外一件事就是调用构造函数,当他调用operator new的时候,其实他的底层还是调用malloc函数来申请的空间,那么这里我们可以通过汇编指令来看看

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

	int _a;
};
int main()
{
	A* pa = new A;
	delete  pa;
	return 0;
}

在这里插入图片描述
那么通过这个截图我们可以看到这个new调用了两个函数一个就是operator new,另外一个就是类中的构造函数,那么这里大家了解一下即可。

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

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

相关文章

Java基础-常用API的使用方法(Math,System,Runtime,Object,BigInteger,BigDecimal)(1)

1 Math类 1.1 概述 tips&#xff1a;了解内容 查看API文档&#xff0c;我们可以看到API文档中关于Math类的定义如下&#xff1a; Math类所在包为java.lang包&#xff0c;因此在使用的时候不需要进行导包。并且Math类被final修饰了&#xff0c;因此该类是不能被继承的。 Math类…

J - 食物链 POJ - 1182

思路&#xff1a; 首先我们要理清楚三种动物之间的关系&#xff0c;那么可以用A到B的距离为1代表为A吃B&#xff0c; 那么就有下图的关系 那么我们用d1表示吃&#xff0c;d2表示被吃&#xff0c;d3表示是同类 对于另一张图也是符合的 然后我们去找每个点和他的根节点的关系 …

resnet(4)------全连接层与softmax

文章目录1. 全连接层2. SoftMax算法1. 全连接层 全连接层&#xff0c;指的是每一个结点都与上一层的所有结点相连&#xff0c;用来把前面几层提取到的特征综合起来。 举个例子&#xff0c;前面通过卷积和池化层提取出来的特征有眼睛鼻子和嘴巴&#xff0c;那我们能单独通过这…

基于tensorflow的深层神经网络(一)为什么神经网络需要解决非线性和异或问题

参考为什么神经网络需要解决多层和非线性问题 - 云社区 - 腾讯云 维基百科对深度学习的精确定义为“一类通过多层非线性变换对高复杂性数据建模算法的合集”。因为深度神经网络是实现“多层非线性变换”最常用的一种方法&#xff0c;所以在实际中基本上可以认为深度学习就是深度…

音频信号特征

1.声音 音信号是由空气压力的变化而产生的&#xff0c;可以测量压力变化的强度&#xff0c;并绘制这些测量值随时间的变化。 声音信号经常在规律的、固定的区间内重复&#xff0c;每个波都具有相同形状&#xff0c;高度表示声音的强度&#xff0c;称之为振幅。 信号完成一个…

论文笔记-时序预测-FEDformer

论文标题&#xff1a;FEDformer: Frequency Enhanced Decomposed Transformer for Long-term Series Forecasting 论文链接&#xff1a; https://arxiv.org/abs/2201.12740 代码链接&#xff1a; https://github.com/DAMO-DI-ML/ICML2022-FEDformer 摘要 尽管基于变压器的方法…

笔记--Ubuntu20.04安装Nvidia驱动、CUDA Toolkit和CUDA CuDNN

目录 1--安装Nvidia驱动 2--安装CUDA 2-1--禁用nouveau 2-2--选择CUDA Toolkit 2-3--下载和安装CUDA Toolkit 2-4--配置环境变量 2-5--测试是否安装成功&#xff1a; 3--安装CUDA CuDNN 4--测试pytorch能否使用Cuda 1--安装Nvidia驱动 ① 查看可安装的Nvidia驱动版本…

Matplotlib学习笔记(第二章 2.13 Matplotlib中的图形(一))

在这里&#xff0c;您将发现大量示例图&#xff0c;其中包含生成它们的代码。 线图(Line Plot) 下面是如何使用plot()创建带有文本标签的线图。 Fig. 1: Simple Plot 多个绘图区域(Multiple subplots in one figure) 多个绘图区域由subplot()函数创建&#xff1a; Fig. 2:…

【小程序】内容滚动方案,视频或者照片上方不随滚动而滚动

&#x1f4ad;&#x1f4ad; ✨&#xff1a;内容滚动方案&#xff0c;视频或者照片上方不随滚动而滚动   &#x1f49f;&#xff1a;东非不开森的主页   &#x1f49c;: 优于别人&#xff0c;并不高贵&#xff0c;真正的高贵应该是优于过去的自己。——海明威&#x1f49c;&a…

【图像去噪】基于自适应滤波器消除椒盐噪声图像附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

web前端课程设计(HTML和CSS实现餐饮美食文化网站)静态HTML网页制作

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

论文笔记-时序预测-Informer

论文标题&#xff1a; Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting 论文链接&#xff1a; https://arxiv.org/abs/2012.07436 源码链接&#xff1a; https://github.com/zhouhaoyi/ETDataset 摘要 许多实际应用都需要对长序列时间序列…

游戏开发55课 性能优化12

4.9 带宽优化 带宽优化的目的是减少CPU与GPU之间的数据传输。 4.9.1 LOD&#xff08;Level Of Detail&#xff09; LOD即细节层次&#xff0c;根据物体在画面的大小选用不同级别的资源&#xff0c;以减少渲染和带宽的消耗。LOD在图形渲染中应用广泛&#xff0c;适用的对象有…

au cs6七线阁教程 笔记

01 驱动 asio 02 I 监听 R录音 一般是单声道 03 调整音量声相 04 导出 删除轨道文件 07 删除选区 自定义波形剪辑颜色 录音激活后&#xff0c;监听才能激活 08 自动控制 默认控制音量&#xff1a; 09 效果器&#xff1a; Vst3 dll 插件 独立效果器面板&#xff0c;实时&a…

[附源码]Python计算机毕业设计SSM基于框架预约挂号系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【leetcode】对称二叉树

一、题目描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 二、代码思路 详细题解地址 思路有…

初识Dockerfile

初识Dockerfile Dockerfile 就是用来构建 docker 镜像的构建文件&#xff0c;命令脚本&#xff01; # 创建一个dockerfile文件&#xff0c;建议 Dockerfile # 文件中的内容 指令(大写)FROM centosVOLUME ["volume01","volume02"]CMD echo "-------e…

【总复习】操作系统

操作系统总复习第1章 操作系统引论第2章 进程的描述与控制第3章 处理机调度与死锁第4章 进程同步第5章 存储器管理第6章 虚拟存储器第7章 输入/输出系统第1章 操作系统引论 1、操作系统的概念&#xff08;定义&#xff09; 操作系统&#xff08; Operating System&#xff0c;…

力扣(LeetCode)1691. 堆叠长方体的最大高度(C++)

动态规划 状态计算 : f[i]{cuboids[i][2]if 不存在kmax(f[k])cuboids[i][2]if k∈[1,i−1]f[i] \begin{cases} cuboids[i][2] &\text{if } 不存在k \\ max(f[k])cuboids[i][2] &\text{if } k \in [1,i-1] \end{cases}f[i]{cuboids[i][2]max(f[k])cuboids[i][2]​if 不…

I-02Python自带编辑器IDLE的使用教程

目录 前言 正文 1. 开启行号功能 2. 运行代码&#xff0c;自动保存 3. 放大字体 4. 附常用快捷键 前言 IDLE是在安装python的时候自带的一个编辑器。 拥有基本的python编程的环境&#xff0c; 常用功能点&#xff1a; 代码自动补齐Debug模式python文档 你肯定想说&…