首页 > 系统相关 >C++内存管理

C++内存管理

时间:2024-04-04 19:59:05浏览次数:26  
标签:malloc 调用 管理 int C++ 内存 operator new delete

前言:

本篇将介绍c/c++的内存空间结构与c++中对内存进行管理的用法,包括new,delete,operator new与operator delete,定位new以及与c中malloc和free的区别等,到stl容器的底层实现篇将会对内存操作进行模拟实现,会进一步加深对内存管理的理解。

目录

前言:

1.new与delete操作符

2.c/c++内存分布

3.operator new与operator delete函数(不是运算符重载

4.malloc/free与new/delete的区别

5.new失败后的抛异常

6.定位new

总结: 文章对c++的内存管理进行了简单的分析与介绍,异常,内存池等内容将在后面介绍。如有错误,请指正!


1.new与delete操作符

开空间:

	//开空间
	int* p1 = (int*)malloc(sizeof(int));
	if (p1 == nullptr)
		perror("malloc fail");

	int* pp1 = new int;

	free(p1);
	delete pp1;
  • new后需要使用delete进行内存释放(调用析构函数),不然一样会造成内存泄漏。
  • new后面跟要开空间的类型。
  • 与malloc相比,方便的是不用手动检查开辟空间失败的问题,这与底层实现有关。

开10个int空间:

	//开10个int空间
	int* p2 = (int*)malloc(sizeof(int) * 10);
	if (p2 == nullptr)
		perror("malloc fail");

	int* pp2 = new int[10];

	free(p2);
	delete[] pp2;
  • 使用new int [10]意思就是开辟10个int空间,申请一个10个int的数组。
  • 注意new开辟的空间为10,是一组对象,所以销毁时要用配套的delete []进行所有元素的销毁即调用每一个元素的析构,也就是delete只会调用第一个数组元素的析构函数。对于基本类型,没有析构函数,二者没有区别。

初始化:

//初始化
int* p3 = new int(10);
int* p4 = new int[10] {1, 2, 3, 4};

delete p3;
delete[] p4;

 p3是开辟或者说申请一个int,初始化为10;p4是开辟10个int,初始化后面给的数据,其它的初始化为0。

对于自定义类型:

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "A()" << endl;
	}
private:
	int _a;
};
int main()
{
	//开空间
	int* p1 = (int*)malloc(sizeof(int));
	if (p1 == nullptr)
		perror("malloc fail");

	int* pp1 = new int;

	free(p1);
	delete pp1;

	//开10个int空间
	int* p2 = (int*)malloc(sizeof(int) * 10);
	if (p2 == nullptr)
		perror("malloc fail");

	int* pp2 = new int[10];

	free(p2);
	delete[] pp2;

	//初始化
	int* p3 = new int(10);
	int* p4 = new int[10] {1, 2, 3, 4};

	delete p3;
	delete[] p4;


	A* paa = new A(2);
	return 0;
}

new对于自定义类型,会调用它的构造函数进行初始化:

 new之后可以传参,调用构造函数初始化:

注意如果没有默认构造,直接new是不行的;如果构造没有参数列表,new时传参也是不行的,都会报错。

new后面可以使用free释放,但是有时会出错,建议都配套使用,这与底层实现有关。

2.c/c++内存分布

这里补充复习一点内存的知识:

内存空间分布:

  •  栈又叫做堆栈,用来存储非静态局部变量/函数参数/返回值等等,栈是向下增长的(也就是从高地址到低地址)。
  • 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库,用户可使用系统接口创建共享共享内存,做进程间通信(linux中的进程地址空间)。
  • 堆用于程序运行时动态内存分配,堆是可以向上增长的。
  • 数据段用来存储全局数据和静态数据。
  • 代码段用来储存可执行的代码/只读常量。

来看3个例子,运用一下:

char char1[] = "abcd";

const char* pChar2 = "abcd";

int* ptr1 = (int*)malloc(sizeof(int));

第一个,对数组名解引用*char,是存放在栈区还是常量区(代码段)?虽然字符串是存放在常量区的,但是数组存放在栈区,这里是将存放在常量区的字符串拷贝到栈区上,且这个字符数组是可以修改的。

第二个,我们要知道const修饰的是pChar2这个指针指向的空间,而pChar2这个指针是存放在栈区的;因为const修饰的是指针指向的空间,所以这块空间是在常量区的,所以这个字符数组不能修改。

第三个,ptr1是指针,是临时变量,存放在栈上的,而解引用*ptr1是代表指针指向的空间,这块空间是动态开辟的,所以是在堆上的。

3.operator new与operator delete函数(不是运算符重载)

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

	//失败抛异常
	int* p1 = (int*)operator new(sizeof(int));

	//失败返回nullptr
	int* p2 = (int*)malloc(sizeof(int));
	if (p2 == nullptr)
		perror("malloc fail");

我们要知道,operator new是malloc的封装,operator delete是free的封装。

new对于内置类型,会直接调用operator new分配内存(也就是调用封装在operator new中的malloc);对于自定义类型,会先调用operator new分配内存,然后再调用构造函数初始化。new[]对于简单的类型,会直接计算出大小进行开空间,对于自定义类型,会在指针的前size_t个字节(不一定是4字节,应该是在指针前size_t个字节(32位是4,64位是8)大小写入数组大小,析构也是析构的指针大小减去这个size_t字节大小指向的空间,也就是说多开的用来存储数组大小的也要释放)写入数组的大小,然后调用对应次的构造函数,也就是说会额外存储数组的大小。

delete对于内置类型会直接调用free函数释放掉开辟的内存,对于自定义类型,会先调用析构函数清理掉new开辟空间中的资源,再调用operator delete也就是调用封装在其中的free释放掉这块开辟的空间。

为什么要封装呢?

机制在于失败了的处理不一样,malloc失败会直接返回空,而new或者delete失败则会抛异常(异常篇再分析)。

对于这样的,new时会先调用operator new []再调用operator new,只是多了一层封装,然后开辟空间,同时开辟空间时会在指针的前4个字节记录数组大小n,在构造时则会构造数组大小n次;delete时也是一样,先调用析构函数,会取出之前的数组大小,然后析构n次,再调用operator delete[],再调用operator delete,也就去调用free释放指针减size_t指向的这块空间(指针开始是指向new开辟的空间的,前面的存储数组大小这块空间也要释放)。 

我们来看看如果不匹配使用操作符一定会报错的场景:

class A
{
public:
	A()
		:_a(nullptr)
	{
		_a = new int;
		cout << "A()" << endl;
	}

	~A()
	{
		delete _a;
	}
private:
	int* _a;
};

int main()
{
	A* p1 = new A[10];
	delete[] p1;//正确

	delete p1;//报错
	
	free(p1);//报错
	return 0;
}

根据之前的知识,我们就能分析: 

第一个正确,调用delete[]时先调用析构,取出之前存储的数组的大小n,析构n次,然后再调用operator delete[],再调用operator delete,也就是调用free释放空间。

第二个报错,原因是没有使用配套的delete[],此时释放指针指向空间的位置就不对了:

前面的空间没有被释放,导致了内存泄露。

第三个报错,没有配套使用,释放的空间位置不对。但是当我们不写析构函数时,编译器觉得析构函数不需要调用,就不会开这4个字节了,所以拿free或者delete释放也就没有问题,但是还是实际还是要配套使用。

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

5.new失败后的抛异常

malloc失败后,开不到2G跳出循环。

new失败,抛异常,在异常篇会补充。

6.定位new

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new (place_address) type或者new (place_address) type (initializer-list)

place_address为一个指针,initialize-list为类型的初始化列表。

class A
{
public:
	A(int a=1)
		:_a(a)
	{

	}
private:
	int _a;
};

int main()
{
	A aa;
	A* p1 = (A*)malloc(sizeof(A));
	if (p1 == nullptr)
	{
		perror("malloc fail");
	}
	
	//对一块已有的空间初始化
	new(p1)A(1);
	return 0;
}

那直接new不比使用malloc再定位new香吗?定位new适用于节省效率的场景,用于内存池申请的内存没有初始化:

先简单了解一下,

直接new就是直接去操作系统的堆去申请空间,然后再返回这块空间,访问慢;

而有了内存池,直接去内存池申请,申请到返回这块空间,如果没有申请到,就去

操作系统的堆上去申请大块内存,再返回给内存池,这样能提升效率。

类比去山下打水就是直接去操作系统申请空间,而去内存池是在家里的蓄水池里打水,

不够了再下山打水。

总结:
 文章对c++的内存管理进行了简单的分析与介绍,异常,内存池等内容将在后面介绍。如有错误,请指正!

标签:malloc,调用,管理,int,C++,内存,operator,new,delete
From: https://blog.csdn.net/2301_79698419/article/details/137211268

相关文章

  • C++ 实验 03
    实验3.1设计一个用来表示直角坐标系的Location类,有两个double型私有数据成员x,y;主程序中,输入相应的值,创建类Location的两个对象a和b,分别采用成员函数和友元函数计算给定两个坐标点之间的距离。【提示】类Location的参考框架如下:classLocation{public:       Loc......
  • 对象存储:现代数据管理的关键技术
    在当今数字化时代,数据的产生和积累呈现出爆炸式增长的趋势。面对海量的数据,传统的存储方式已经无法满足日益增长的需求。为了有效管理和利用这些数据,对象存储技术应运而生。对象存储分为三种类型,第一种:标准存储,较适合用于云应用,数据分享,内容分享,热点分享等等浏览频率较高的存......
  • 【c++初阶】类与对象(下)
    ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨......
  • 【VMware vCenter】修改vCenter Server管理后台VAMI登录用户ROOT的密码策略。
    vCenterServer作为家庭实验室环境时,可以设置一个简单且容易记住的密码,而不需要考虑那么多的安全性要求。默认情况下,vCenter的密码强度要求比较高,SSO用户还好可以修改,VAMI账户root并没有提供可以修改的地方。vCenter的SSO用户密码策略可以在vSphereClient-系统管理-SingleSign......
  • Qt C++ | Qt 元对象系统、信号和槽及事件(第一集)
    01元对象系统一、元对象系统基本概念1、Qt的元对象系统提供的功能有:对象间通信的信号和槽机制、运行时类型信息和动态属性系统等。2、元对象系统是Qt对原有的C++进行的一些扩展,主要是为实现信号和槽机制而引入的,信号和槽机制是Qt的核心特征。3、要使用元对象系统......
  • 基于ssm会议管理系统论文
    摘要现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本会议管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,......
  • 基于ssm的学生公寓管理中心系统的设计与实现论文
    摘要现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本学生公寓管理中心系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务......
  • 基于ssm农产品仓库管理系统系统论文
    摘要现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本农产品仓库管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处......
  • 《C++程序设计》阅读笔记【2-程序结构】
    ......
  • 软件测试和质量管理——实验3:单元测试
    《软件测试和质量管理》实验报告三一、目的和要求        1、掌握单元测试技术,并要求按照单元测试的要求设计测试用例;        2、掌握在Eclipse里进行Junit4测试的技术;        3、根据题目要求编写测试用例;        4、实验结果要求给出测......