C++4:C++中的动态内存管理

目录

C++内存管理方式

 new/delete操作内置类型:

 new/delete操作自定义类型:

new和delete的底层实现:

operator new与operator delete函数

定位new

内存泄漏

动态内存管理,早些我们接触C语言的时候就已经在很熟练的游玩在堆上开空间的操作了,我们最开始的应用莫过于数据结构顺序表的实现,其中的扩容我们已经再熟悉不过了

//扩容
    if (slt->size == slt->capacity)
    {
        int newcapacity = slt->capacity == 0 ? 4 : slt->capacity * 2;
        Datatype* tmp = (Datatype*)realloc(slt->a, newcapacity * sizeof(Datatype));
        if (realloc == NULL)
        {
            perror("realloc fail!");
            exit(-1);
        }

        slt->a = tmp;
        slt->capacity = newcapacity;
    }

但是我们总是绕不开开辟完毕的返回值需要判空的问题

那么C++作为C的升级,在动态内存管理上是否有什么讨喜的升级呢?

C++内存管理方式

 new/delete操作内置类型

C++中开辟内存空间的关键字升级成了new,相较于C语言较为繁琐的申请格式,new则简单粗暴的多,其释放也非常简单。

有如下三种使用new的方式

void Test()
{
    // 动态申请一个int类型的空间
    int* ptr4 = new int;

    // 动态申请一个int类型的空间并初始化为10
    int* ptr5 = new int(10);

    // 动态申请10个int类型的空间
    int* ptr6 = new int[3];

    delete ptr4;
    delete ptr5;
    delete[] ptr6;
}

 new和delete的格式一定要对齐,也就是如果使用了new[]就必须对应着使用delete[]


 new/delete操作自定义类型

对于自定义类型,new会直接调用其构造函数,delete会调用其析构函数,而malloc和free则不会。

malloc过大的空间的时候会直接崩溃,而new则是抛异常,由于牵扯到多态和继承的问题,不记述。

示例: 


new和delete的底层实现:

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

既然new是调用operator函数来实现的,那么这个全局函数又是怎么实现的?


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->nBlo
		_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); 
		__END_TRY_FINALLY
		return;
	}

原码虽然看上去很复杂,但是我们也能在里面找到一些老熟人,比如malloc和free,这下我们知道了,new和delete的底层实现也是依靠于malloc和free实现。

那么问题来了,C++弄这么大一坨封装malloc的意义在哪?

答:封装malloc的意义则是申请内存失败就抛异常

那么总结一下new对内置类型和自定义类型的实现原理

对于内置类型:

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

对于自定义类型:

new的原理
1. 调用operator new函数申请空间
2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理
1. 在空间上执行析构函数,完成对象中资源的清理工作
2. 调用operator delete函数释放对象的空间

new T[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
2. 在申请的空间上执行N次构造函数

delete[]的原理
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间


定位new

定位new可以直接调用某一个类里的构造函数,但是它需要一个地址以及其对象的类。

那么如何销毁这块空间呢?

析构函数可以显式调用,可以直接调。

直接delet这块空间也是可以的,因为delete的底层依旧是free和调用析构函数,本质上和上面两段代码没有本质区别

当然以上的开辟方法其实还是完全不敌new。


内存泄漏

内存泄漏的本质我个人更加愿意理解为空间的遗忘,内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
虽然说对正常结束的程序影响不大,但是对长期运行的程序就是定时炸弹。被遗忘的空间会越来越大,最终导致占用过多的内存导致程序崩溃

如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
能指针来管理才有保证。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵
 


以上就是C++中的动态内存管理的概述了!希望对你有点帮助!感谢阅读!

猜你喜欢

转载自blog.csdn.net/m0_53607711/article/details/128559276