C++——内存管理

目录

一,C/C++内存分布

二,C++中动态内存呢管理

2.1 new和delete操作内置类型

2.2 new和delete操作自定义类型

2.3 优化数据结构链表

2.4 new()和new[]

2.5 关于栈的释放 

三,operator new和operator delete函数

四,new和delete的实现原理

4.1 内置类型

4.2 自定义类型

五,定位new

六,常见面试题

6.1 malloc/free和new/delete的区别

6.2 关于内存泄漏


 

一,C/C++内存分布

先结合下面的代码和题目初步认识一下各种变量的存储位置

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在哪里?__c__   staticGlobalVar在哪里?__c__
   staticVar在哪里?__c__   localVar在哪里?__a__
   num1 在哪里?__a__
   
   char2在哪里?__a__   *char2在哪里?_a__
   pChar3在哪里?__a__      *pChar3在哪里?__d__
   ptr1在哪里?__a__        *ptr1在哪里?__b__

关于上面图片右侧框框的说明:

又叫堆栈,存储非静态局部变量,函数参数以及返回值等等,栈是向下增长的

内存映射段是尬笑的IO映射方式,用于装载一个共享的动态内存库。给用户提供系统接口,创建共享内存,做进程通信(这部分属于Linux操作系统知识)

用于程序运行时的动态内存分配,向上增长

数据段(静态区)存储全局变量数据和静态数据

代码段(常量区)存储可执行的代码,以及只读const常量数据 

二,C++中动态内存呢管理

2.1 new和delete操作内置类型

C语言中实现动态内存管理的方式有 malloc/calloc/realloc/free这几个函数

在C++中,动态内存管理函数只有两个: new/delete 如下代码

void main1()
{
	//下面两种开辟空间的方式相同
	int* pp1 = (int*)malloc(sizeof(int));
	int* p1 = new int;
	free(pp1);
	delete p1;

	int* pp2 = (int*)malloc(sizeof(int) * 10);
	int* p2 = new int[10];
	free(pp2);
	delete[] p2;
}

申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的一块空间,使用new[]和delete[],而且要注意,必须匹配使用,不能new之后用delete[]释放,也不能new[]之后delete释放,必须匹配使用,否则会出大乱子

2.2 new和delete操作自定义类型

先观察下列代码和运行结果

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

void main2()
{
	A1* p1 = (A1*)malloc(sizeof(A1));
	A1* p2 = new A1(1);  //开空间+调用构造函数初始化
	free(p1);
	delete p2; //释放空间并调用析构函数
}

 可以发现,与C语言malloc和free不同的是,在调用new的时候会先去调用对象的构造函数,delete的时候会去调用对象的析构函数,这就使new和delete比malloc和free多了更多强大的功能

2.3 优化数据结构链表

在数据结构阶段,我们实现链表的时候,每次搞链表节点的时候都需要一个ListNode BuyListNode(int x){} 来动态开辟空间然后初始化,但是我们有了new之后,只需要在实现节点的时候加上构造函数,这样在初始化节点的时候可以直接使用new来调用构造函数直接完成对象创建的初始化,如下代码

struct ListNode
{
	int _val;
	ListNode* _next;

	ListNode(int val)
		:_val(val)
		,_next(nullptr)
	{}
};

void main3()
{
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
}

2.4 new()和new[]

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	//explict关键字,加在构造函数前面就可以紧张隐式类型转换
	//explicit A(int a)
	A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	explicit A(int a1, int a2)
		:_a1(a1)
		, _a2(_a2)
	{
		cout << "A(int a1,int a2)" << endl;
	}
	A(const A& aa) //产生临时对象
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1;
	int _a2;
};
void main4()
{
	int* p1 = new int;       //不会初始化
	int* p2 = new int(10);   //申请一个int,初始化为10
	int* p3 = new int[10];   //申请一个数组,里面有10个int
	int* p4 = new int[10] {1, 2, 3, 4}; //列表初始化
	A* p5 = new A[10];       //申请一个数组,里面有十个A,调用十次构造函数
	delete[] p5;
}

2.5 关于栈的释放 

class Stack
{
public:
	Stack()
	{
		cout << "Stck()" << endl;
		_a = new int[4];
		_top = 0;
		_capacity = 4;
	}
	~Stack()
	{
		delete[] _a;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

void main5()
{
	Stack st; //自己调用构造和析构

	Stack* pst = new Stack;  
	//new的时候在堆上开一块空间,这个空间里存int* _a指针,然后构造函数再在堆上申请空间,然后把指针赋值给_a
	//但是析构的时候只析构了_a指向的那块堆空间,_a本身没有释放
	//所以需要在析构函数中再次delete掉_a指向的那块空间
	delete pst;
	
	A* p = new A[10];
	delete[] p; //VS申请40个字节,实际会多生成四个字节,也就是一个int用来存申请的个数
}

三,operator new和operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的两个全局函数,new在底层调用operator new,然后operator new再在底层调用malloc来实现空间申请,delete也是如此,所以不要觉得new是个非常非常高大上的东西,其实它也是通过层层调用来实现功能的。

如果malloc申请成功就直接返回,否则抛异常。

void msin6()
{
	//失败了抛异常
	int* p1 = (int*)operator new(sizeof(int*));
	
	//失败了返回nullptr
	int* p2 = (int*)malloc(sizeof(int*));
	if (p2 == nullptr)
	{
		perror("malloc fail");
	}

	//C++兼容C,new相比于C的malloc要添加的特性
	//1,申请空间      operator new -> 封装malloc
	//2,调用构造函数
	A* p3 = new A;

	//delete
	//先调用析构函数,再释放空间

	//在释放空间的时候,new用delete,malloc用free,尽量不要交叉使用
	delete p1;
	free(p2);

	//先调用析构函数,再释放空间
	delete p3;
}

四,new和delete的实现原理

4.1 内置类型

如果申请的是内置类型的空间,new/delete和malloc/free基本类似,但不同的地方是:

new/delete申请和释放的是单个元素的地址,new[]和delete[]申请和释放的是连续的空间,而且new申请空间失败时抛异常,malloc会返回NULL

void main7()
{
	size_t size = 0;
	try
	{
		while (1)
		{
			int* p1 = new int[1024 * 1024];
			size += 1024 * 1024 * 4;
			cout << p1 << endl;
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	cout << size << endl;
	c

4.2 自定义类型

new:

①调用operator new函数申请空间

②在申请的空间上执行对象的构造函数完成初始化

delete

①在空间上先执行析构函数,完成清理工作

②调用operator delete函数释放空间

new T[N]

①调用operator new[]函数,完成对数个对象的空间申请

②在申请的数个空间上执行相同数量的构造函数

delete[]

①先进行N次析构

②调用operator delete[]释放多个空间

五,定位new

定位 new 表达式是在 已分配的原始内存空间中调用构造函数初始化一个对象
void main8()
{
	A aa;
	A* p1 = (A*)malloc(sizeof(A));
	if (p1 == nullptr)
	{
		perror("malloc fail");
	}
	//对一块已有的空间初始化 -- 定位new
	new(p1)A(1);
	p1->~A();
	free(p1);
	//平常一般不会先malloc再去初始化,而是直接new申请空间+初始化
	//在操作系统的堆空间上申请会慢一些,所以我们直接去内存池申请
	//如果需要频繁申请空间,一般会去内存池申请
}

六,常见面试题

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

6.2 关于内存泄漏

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


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值