C++内存管理

175 阅读1分钟

一 内存分布

--前言

平时写的代码,都是写在文件中,存储在磁盘.

经过编译链接生成的可执行程序,也是一个文件(二进制),存储在磁盘上.

此时可执行程序里写的主要是 二进制指令代码 和 数据【包括全局变量、常量】.

--空间分布

程序加载到内存,将数据加载到对应的位置

image.png

栈:也叫堆栈,存储非静态局部变量、返回值、函数参数等

栈区域的变量空间,是在进程运行,建立栈帧时开辟的.

堆:用于动态内存分配,堆上的空间也是在进程运行时开辟的.

数据段/静态区:存储全局数据和静态数据,程序刚加载到内存,就要把全局和静态数据加载进来.

代码段/常量区:存储二进制指令代码 和 常量,程序刚加载到内存,就要把它们加载进来.

二 C++动态内存管理

只有堆区的数据,需要我们手动申请,手动释放,否则会造成内存泄漏.

c语言的malloc/calloc/realloc函数仍然可以用,

同时c++引入了 new/delete 操作符,来进行动态内存管理.

--用法

对内置类型

void test()//针对内置类型
{
	//动态申请一个int空间
	int* p1 = new int;
	cout << *p1 << endl;
	delete p1;//手动释放

	//动态申请一个double空间,初始化为1.1
	double* p2 = new double(1.1);
	cout << *p2 << endl;
	delete p2;


	//动态申请多个int
	int* p3 = new int[3];
	for (int i = 0;i < 3; i++)
	{
		cout << p3[i] << "        ";
	}
	cout << endl;
	delete[] p3;//new和delete匹配,new[]和delete[]对应匹配

	//动态申请多个int,并用{}初始化(c++11支持)
	int* p4 = new int[4]{1, 2, 3};//剩下元素都会初始化为0
	for (int i = 0; i < 4; i++)
	{
		cout << p4[i] <<" ";
	}
	delete[] p4;
}

image.png

结论

针对内置类型,new/new[ ]仅仅只是开空间,且更方便,并不会自动初始化内置类型.

对自定义类型

class A
{
public:
	A()
	{
		cout << "调用构造函数" << endl;
	}
	~A()
	{
		cout << "调用析构函数" << endl;
	}
};

  //申请一个A类空间
	A* a1 = new A;
	delete a1;
	cout << endl << endl;

	//申请多个A类空间
	A* a2 = new A[4];
	delete[] a2; //匹配new[] 和delete[]

image.png

结论

针对自定义类型,除了申请和释放空间,

new会去调用它的构造函数,delete会去调用它的析构函数.

但malloc和free不会去调用.

image.png

注意

不传参数调用的是默认构造函数,传参数可以选择调用对应的构造函数

class A
{
public:
	A(int a)
	{
		cout << "调用构造函数1" << endl;
	}
	A()
	{
		cout << "调用构造函数2" << endl;
	}
};
void test3()
{
	A* a1 = new A(1);
	A* a2 = new A;
  delete a1;
	delete a2;
}

image.png

new申请内存失败

new失败后,不需要检查返回值,它失败后是抛异常,此时需要捕获异常

image.png

void test4()
{
	try
	{
          //new申请空间发生异常,立刻跳转到catch,不会再向下执行,否则无视catch
		char* a = new char[1024 * 1024*1024u*2 - 1];
                
		cout << a << endl;
	}
	catch (const exception& e)
	{
		//打印出异常内容
		cout << e.what() << endl;
	}
}

--底层原理【重点】

以下是new一个A类对象的汇编代码(部分),【注意new是一个关键字/操作符】

会发现它先调用了名为operator new的函数,再调用A构造函数.

image.png

operator new函数

内部封装malloc函数帮助【操作符new】开空间;

同时实现抛异常的机制.(全局函数)

operator delete函数

内部封装free函数帮助【操作符delete】释放空间,

与operator new配对.(全局函数)

cplusplus.com里的函数原型

image.png

image.png

结论

new操作符

1 先底层调用 operator new函数 在堆上开空间,并实现抛异常机制;

2 再调用构造函数初始化自定义类型.

3 operator new函数内部调用了malloc函数

【先在堆区开了空间,才能初始化里面的类对象】

delete操作符

1 先调用析构函数清理对象空间.

2 底层调用 operator delete函数 释放空间;

3 operator delete函数内部调用了free函数

【先把堆里的对象内部空间清理后,再free掉该堆区空间】

--重载operator new和operator delete

可以在类里重载类专属operator new和operator delete

例如:在ListNode里重载了operator new函数,

在new这个类时,new调用的operator new函数就是该类专属的.

class ListNode
{
public:
	ListNode(int val = 0)
		:_val(val)
		,_next(nullptr)
	{}
	void* operator new(size_t size)
	{
		cout << "使用ListNode专属的operator new" << endl;
          //使用域作用限定符指定全局域里的operator new
		void* ret = ::operator new(size);
		return ret;
	}
	void operator delete(void* _block)
	{
		cout << "使用ListNode专属的operator delete" << endl;
		::operator delete(_block);
	}
private:
	int _val;
	ListNode* _next;
};
void test7()
{
	ListNode* n1 = new ListNode(1);
	delete n1;
}

image.png

--定位new表达式

某些场景下,空间已经开辟出来了,但还没有初始化。例:

A* a1 = (A*)malloc(sizeof(A));

此时可以手动调用构造函数初始化这块空间吗?

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

定位new表达式格式:

new(空间地址)type(参数列表)

例:

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

void test8()
{
	A* a1 = (A*)malloc(sizeof(A));
	//定位new格式,手动调用构造函数
	new(a1)A(1);
	//手动调用析构函数
	a1->~A();
	free(a1);

	A* a2 = (A*)malloc(sizeof(A));
	//不给参数就调用默认构造
	new(a2)A;
	a2->~A();
	free(a2);
}

image.png

不能一次性初始化多个:

image.png