一文带你掌握C++之动态内存管理

目录

程序中的内存划分

C语言中动态开辟内存的方式

C++中动态开辟内存的方式

 new和delete的底层实现原理

内存泄漏


在讲述内存管理之前先带领大家了解一下程序中的内存划分:

程序中的内存划分

程序中内存划分的示意图如下:

一文带你掌握C++之动态内存管理_第1张图片

内核空间:用户代码不可读写,不可存放用户自定义数据。

栈:存放非静态局部变量/形参变量(函数调用会产生栈帧的消耗,形参变量是在栈帧中所创建的)/返回值的拷贝(值传递会对返回的变量生成一份拷贝,生成的拷贝变量是在栈区生成的)。栈的空间很小。

堆:用于程序动态内存分配,堆的空间是很大的。

数据段:存放全局数据和静态数据,就是我们认识的静态区。

代码段:存放只读常量,如常量字符串。

C语言中动态开辟内存的方式

在C语言动态内存开辟那一期我们讲过,我们一般使用,malloc,calloc,realloc函数开辟空间,用free函数释放空间,注意这四个都是函数,malloc是直接开辟一块新的空间,calloc也是直接开辟空间,但是在开辟好空间之后,会对开辟好的空间按字节序全部初始化为0,realloc是对之前已经开辟好的空间进行扩容。

C++中动态开辟内存的方式

在C语言中我们是使用四个函数去动态开辟和释放空间,但是在C++中我们使用两个关键字newdelete开辟和释放空间。new用于开辟空间delete用于释放空间

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


private:
	int _a;
};

int main()
{

	A* p1 = (A*)malloc(sizeof(A));
	A* p2= new A(10);
	A* p3 =(A*) malloc(sizeof(A) * 10);
    //C++98只支持下面这种创建对象的方式
    A* p4 = new A[10];    
    //C++11支持下面这种创建了多个对象进行赋值的方式
	A* p4 = new A[10]{1,2,3,4,5,6,7,8,9,10};
    free(p1);
	delete p2;
	free(p3);
	delete []p4;


	return 0;


}

p1,p3指向了由用C语言的方式动态开辟的一个对象和多个对象,而p2,p4指向了用C++方式动态开辟的一个和多个对象,我们用C语言的方式通过free函数释放了指针p1,p3,我们用C++的方式通过delete和delete[]的方式释放了指针p2和p4。

注意不论是C语言中的malloc还是C++中的new,都是在堆上开辟空间,所以最终一定要释放空间,不能会导致内存泄漏。malloc和free,new和delete,new[]和delete[],这三种组合一定要匹配使用,不然会报错。

          

既然我们也可以在C++使用C语言动态开辟空间的方法,为什么我们还要引入C++的动态开辟空间的方法呢?

具体的原因由两个:

1.两者是有区别的。

先运行如下代码:

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


private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	free(p1);
	return 0;
}

运行结果如下:

 9859bbb6f5cf4bb781795f8adc836262.png

我们发现,没有打印构造函数和析构函数。

我们再运行如下代码:

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


private:
	int _a;
};

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

 运行截图如下:

  df6935c565ce4ca6ad7aa8b5c02ce29e.png

我们发现其打印了构造函数和析构函数,我们之前在使用类类型创建对象时,编译器同时也会调用构造函数,在对象周期快要结束时,编译器也会调用析构函数进行对象资源的清理。

所以,为什么在C++中要使用new和delete开辟和释放空间的第一个原因就已经很明显了,因为C++中使用new和free关键字开辟和释放空间时,会调用构造函数进行初始化和调用析构函数进行资源的清理。这里需要注意:先使用new开辟空间然后调用构造函数进行初始化,先调用析构函数进行资源的清理然后再使用delete释放空间,一定要注意构造函数,析构函数和new,delete的执行先后顺序。

对于内置类型而言,没有构造函数和析构函数。所以: 

对于内置类型而言,使用C语言和C++动态开辟空间的方式是都可以的,但是如果是自定义类型而言,我们建议使用C++的动态开辟内存的方式,因为会对对象进行和初始化和最后对资源进行清理。

我们建议不管是自定义类型和内置类型,在C++中都使用new和delete的方式进行空间的申请和释放。

2.处理错误的机制不同

因为C语言是面向过程的编程语言,所以其一般的处理方式就是返回错误码,但是在C++中处理错误的方式是抛异常,如果使用malloc进行空间的开辟,开辟成功了好说,开辟不成功会返回错误码告知内存空间开辟失败,但是在C++中是不会识别这个错误码的,所以也就不会识别malloc开辟空间失败的情况,所以我们必须使用C++中专门的开辟空间和释放空间的方法,即使用new和delete关键字进行空间的开辟和释放。

以上便是为什么要在C++中使用new和delete申请和释放空间的方式。 

 new和delete的底层实现原理

new和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 delete: 该函数最终是通过free来释放空间的
*/
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 new和operator  delete实质上也是调用malloc和free函数进行资源的清理和释放,不过,此时的malloc开辟失败不再返回错误码,而是抛出异常,这也就符合了C++处理错误的机制。

总结:operator new和operator delete实质上就是对malloc和free的封装和优化,使得malloc开辟空间失败发生错误时返回错误码的方式转换成了抛异常,符合了C++处理错误的机制。

内存泄漏

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

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

产生内存泄漏主要有两种场景:

int main()
{
	//申请了空间,最后却没有释放
	A* p1 = new A(5);
	//可能在delete之前,产生了错误,编译器抛出了异常导致程序结束,从而使得delete没有执行,而导致内存泄漏
	A* p2= new A(10);
	func();
	delete p2;
	return 0;
}

所以我们在使用new和malloc动态开辟了空间时,一定要使用free和delete进行空间的释放,以免造成内存泄漏,产生危害。

怎样解决内存泄漏的问题呢?

我们一般会使用智能指针的方式解决内存泄漏的问题,一般会解决大部分的问题,至于怎样使用智能指针解决内存泄漏,智能指针是C++11的内容,所以在后期会为大家讲解。 

本期内容主要讲述了new和delete与malloc和free的区别。

new和delete与malloc和free的区别:

1.new和delete是关键字,而malloc和free是函数。

2.对于内置类型而言,这两者没有什么区别,但是对于自定义类型而言,new和delete会调用构造函数和析构函数,但是malloc和free并不会调用。

3.malloc函数的返回值要强转成空间的地址的类型,而new直接跟空间类型名称就行,不需要强转类型。 

4.malloc开辟空间失败,返回NULL,而new则是抛出异常。

以上便是C语言和C++在开辟空间和释放空间时的区别。

本期的内容到此结束^_^ 

你可能感兴趣的:(知识总结,C++,c++,编程语言)