C++内存管理

Posted 正义的伙伴啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++内存管理相关的知识,希望对你有一定的参考价值。

文章目录

C++内存管理

C++内存分布(四大内存分区)


下面依次简单的介绍一下各个分区:

  • 栈:主要存储函数参数,局部变量,栈区的大小实际上大概只有8M
  • 堆:存储动态开辟的变量,堆的大小比栈大多了,所以一些较大的数据一般都存在堆上面
  • 数据段:存储全局变量,静态变量
  • 代码段: 存储可执行代码、只读常量(例如字符常量)

int main()

	const char* a = "abc";
	char b[] = "abc";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	free(ptr1);
	// a在哪里: 栈区
	// *a在哪里: 代码段
	//  b在哪里:栈区
	//  *b在哪里:栈区
	//  ptr1在哪里: 栈区
	//  *ptr1在哪里:堆区

C++中内存管理的方式

C++用关键字:new 和 delete来申请和释放空间

new初始化:

int* ptr = new int;   //直接开辟一块空间
int* ptr1 = new int(5); //初始化的时候直接赋值
int* ptr2 = new int[10];//开辟十个连续空间(这要求int有默认构造函数)

这就是new的三种定义方式

delete 用法:

delete ptr;
delete ptr1;//回收一段空间
delete[] ptr2;//回收一段连续的空间,相当于连续调用delete

但是这里new、delete 和 malloc、free好像并没有什么区别?

  1. 对于内置类型:两者没有什么区别
  2. 如果是自定义类型,new、delete会调用类的构造和析构函数申请和释放空间,但是malloc、free不会

operator new和operator delete

operator new和operator delete是一个全局函数,而new和delete是关键字,注意两者本质上的区别!

这是operator new的源代码

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 delete的源代码:

_free_dbg( pUserData, pHead->nBlockUse );
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

我们发现operator new 和 operator delete其实底层都是调用malloc和free来申请和释放空间的

operator new、operator delete与malloc和free的区别是什么?

区别在于处理错误的方式不同:malloc开辟失败返回的是空指针,但是operator new开辟空间失败是抛出异常,我们可以理解成
operator new=malloc+失败时抛出异常
operator delete=free + 失败时抛出异常

new、delete和operator new、operator delete有什么区别?

new、delete会调用类的构造和析构函数申请和释放空间,但是operator new 、operator delete不会,实际上:
new=operator new+构造函数
delete=析构函数 + operator delete

重载operator new、operator delete

上面讲了operator new、operator delete是一个全局的函数,我们还可以对operator new 和 operator delete在类里面进行重载

以达到自己的一些需求,operator new和operator delete有两个重载版本:

void* operator new (size_t t);

void* operator new[] (size_t t);

void operator delete (void* p);

void operator delete[] (void * p);

这是在类里面的重载时的类型,然后我们在这些重载类型中加入一些自己想实现的功能,我们在外面调用时按照new、delete的写法去调用,这时会编译器会调用重载的operator new或operator delete 和 构造函数 或 析构函数

调用顺序的确定

class A

public:
	static int _c;
	A(int x=0)
		:_b(x)
	
		cout << "构造函数" << endl;
		_a = new int[5];

	

	void* operator new(size_t t)
	

		cout << "调用 operator new" << endl;
		A* p = (A*)::operator new(t);
		_c++;
		return p;
	

	void operator delete(void* p)
	
		cout << "调用 operator delete" << endl;
		::operator delete(p);
		_c--;
	

	~A()
	
		cout << "析构函数" << endl;
		free(_a);
		_a = NULL;

	
private:
	int* _a;
	int _b;
;

int A::_c = 0;


int main()

	A* p = new A(1);
	delete p;


这里就可以知道:在A* p = new A(1);时底层是先调用operator new再调用构造函数,而在delete p;时正好反过来,先调用析构函数再调用operator delete。

应用:检查空间是否完全释放


class A

public:
	static int _c;
	A(int x=0)
		:_b(x)
	
		//cout << "构造函数" << endl;
		_a = new int[5];

	

	void* operator new(size_t t)
	

		//cout << "调用 operator new" << endl;
		A* p = (A*)::operator new(t);
		_c++;
		return p;
	

	void operator delete(void* p)
	
		//cout << "调用 operator delete" << endl;
		::operator delete(p);
		_c--;
	

	~A()
	
		//cout << "析构函数" << endl;
		free(_a);
		_a = NULL;

	
private:
	int* _a;
	int _b;
;

int A::_c = 0; //定义一个静态变量,如果调用构造函数就++,没调用就--,如果程序结束时_c为0,则说明内存全部释放

int main()

	A* ps1 = new A(1);
	A* ps2 = new A(2);
	A* ps3 = new A(3);
	A* ps4 = new A(4);
	A* ps5 = new A(5);
	A* ps6 = new A(6);
	A* ps7 = new A(7);

	delete ps1;
	delete ps2;
	delete ps3;
	delete ps4;
	delete ps5;
	delete ps6;
	cout << A::_c << endl;


这里就可以看到ps7没有释放,所以最后结果不为0;

总结:

我的理解是:operator new、operator delete(全局) 给内存申请、释放提供了一个自定义的方法,我们可以在类里面自定义operator new、operator delete来实现一些别的功能,其他好像也并不是很有用

定位new表达式

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
因为类的初始化一定是伴随着类的定义,所以就无法给已经初始化好的对象赋值,placement new正好完成了这个任务

使用格式:new(place_address) type;new(place_address) type(initializer_list);

place_address 必须是一个指针,type是初始化变量的类型,initializer_list是类型的初始化列表

     int* p = new int[10];//这里用new[]开辟的空间并不能在开空间的时候赋初值

	for (int i = 0; i < 10; i++)
	
		new(p+i) int(5);
	

	for (int i = 0; i < 10; i++)
	
		cout << p[i] << " ";
	

内存泄漏

先看一个问题:

class A

public:
	static int _c;
	A(int x=0)
		:_b(x)
	
		//cout << "构造函数" << endl;
		_a = new int[5];
	

	void* operator new(size_t t)
	
		//cout << "调用 operator new" << endl;
		A* p = (A*)::operator new(t);
		_c++;
		return p;
	

	void operator delete(void* p)
	
		//cout << "调用 operator delete" << endl;
		::operator delete(p);
		_c--;
	

	~A()
	
		//cout << "析构函数" << endl;
		free(_a);
		_a = NULL;
	
private:
	int* _a;
	int _b;
;



int A::_c = 0;

int main()

	A a;
	A* p = new A;

	

这里我们要搞清楚 类a 和 类a的成员变量 以及 指针p 、指针p所指向的类 、指针p所指向的类的成员变量 所在的空间

弄清楚这个问题会对delete有更深的理解:为什么要先调用析构函数,在销毁空间!

在析构的时候:调用析构函数先把 蓝色_a所指向的空间 释放掉,在销毁p在堆上的空间,如果先销毁p在堆上的空间, 蓝色_a所指向的空间 就无法释放造成内存泄漏。

上面的问题提到的内存泄漏,是一个很头疼的问题

如果像 蓝色_a所指向的空间(泛指各种不规范操作造成的内存无法释放)积累到一定程度,就会造成堆上无法在申请到空间。
但是有些时候我们发现一些内存泄漏在程序结束之后就会“自动消失”,这是因为一个进程正常结束后,内存就会被释放掉,但是设想在一些24h运行的服务器上进程不会结束,这样内存泄漏的危害就会很大了

malloc/free 和 new/delete区别的总结

共同点:都是手动从堆上面开辟空间
不同点

  1. malloc和free 是函数,而new和delete是操作符
  2. malloc申请空间时需要手动计算大小并传递,而new只需要输入类型和个数即可
  3. malloc的返回值是void *,使用前必须强转,new不需要,因为new后面跟的就是类型
  4. malloc申请失败会返回空指针,因此使用前必须要判空,new申请失败会抛出异常
  5. 申请自定义对象时:malloc和free只负责开辟空间,不会调用构造或析构函数,而new申请空间会先开辟空间再调用构造函数,delete会先调用析构函数再释放空间

以上是关于C++内存管理的主要内容,如果未能解决你的问题,请参考以下文章

[ C++ ] 一篇带你了解C++中动态内存管理

c++中的动态内存管理

C++内存管理+模板

C++入门篇之内存处理

C++入门篇之内存处理

C和C++内存管理(newmalloc和freedelete)