C++内存管理

C++内存分布

分布图

在这里插入图片描述

说明
  1. 栈(堆栈):非静态局部变量/函数参数/返回值等等,栈是向下增长的;
  2. 内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信;
  3. 堆:用于程序运行时动态内存分配,堆是可以向上增长的;
  4. 数据段:存储全局数据和静态数据;
  5. 代码段:可执行的代码/只读常量;

内存管理

内置类型
C语言
  • void* malloc(总字节数):申请指定字节大小的空间,且无初始化,返回一个void*指针指向空间首地址,因此在使用时需要强制转换;
  • void* realloc(动态开辟空间的地址, 调整后的字节大小):在动态申请的空间的基础上,进行空间的扩容,如果第一个参数为NULL,那么作用和malloc一样;被扩容的指针不需要我们进行释放,我们只需释放扩容后返回的指针;
  • void* calloc(申请元素的个数, 元素的大小):申请元素个数 * 元素大小的空间,并且将空间内容初始化为全 0;
void Test (){
	int* p1 = (int*) malloc(sizeof(int));
	free(p1);
	int* p2 = (int*)calloc(4, sizeof (int));
	int* p3 = (int*)realloc(p2, sizeof(int)*10);
	free(p3 );
}
C++
  • type* p = new type:申请一个指定类型的元素空间;
  • type* p = new type(初始化内容):申请指定类型的单个空间,并初始化为指定内容;
  • delete p:释放申请的单个空间;
  • typr* pp = new type[元素个数]:申请指定元素个数的连续空间;
  • delete[] pp:释放动态申请的连续空间;
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;
}
自定义类型

  通过上面的内容,大家可以发现malloc / freenew / delete其实差别并不是太大,主要是malloc / free使用起来更麻烦一些,所以大家可能感觉new / delete的存在可有可无,但是其实并非这么简单;
  当我们申请自定义类型空间时,就可以明显地发现,当使用malloc时,创建出来的对象并没有初始化,也就是创建对象时没有自动调用构造函数,而在使用new创建对象时,当对象创建好后,会自动调用构造函数,为对象初始化;并且当申请连续的自定义空间时,空间开辟好后,会自动连续的的调用构造函数,为每一个对象初始化;
  同理,当时用free释放对象空间时,并不会调用其析构函数来释放资源空间,当时用delete时,则会自动调用对象的析构函数来释放资源,然后释放申请的空间;且当释放连续空间时,会自动连续的调用析构函数,为每一个对象释放资源,然后再将申请的空间进行释放;

class Test{
public:
	Test()
		: _data(0)
	{
		cout<<"Test():"<<this<<endl;
	}
	~Test(){
		cout<<"~Test():"<<this<<endl;
	}
private:
	int _data;
};
void Test2(){
	// 申请单个Test类型的空间
	Test* p1 = (Test*)malloc(sizeof(Test));
	free(p1);
	// 申请10个Test类型的空间
	Test* p2 = (Test*)malloc(sizoef(Test) * 10);
	free(p2);
}
void Test2(){
	// 申请单个Test类型的对象
	Test* p1 = new Test;
	delete p1;
	// 申请10个Test类型的对象
	Test* p2 = new Test[10];
	delete[] p2;
}

operator newoperator delete

函数讲解

  operator newoperator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator 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:
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实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的。

定制函数
内存池技术是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的几个显著优点:(内存池技术只是简单的介绍一下,后面还会深入的学习的)
1.减少了与 OS 的交互,使得内存分配效率得到提升;
2.申请一大块空间,然后再根据需求从这块空间中拿出需要的,这样可以大大减少内存碎片的存在;
3.因为这是提前就申请好的,所以在使用时可以不需要去释放空间,等到最后再一次性释放空间,避免了内存泄露的问题;

  定制函数就是对于那些需要频繁申请内存的类,通过重载类专属operator newoperator delete函数,实现使用内存池技术申请和释放内存,提高效率;在类中重载类专属operator newoperator delete函数可以屏蔽掉与之对应的全局函数,这样在申请空间时,会自动调用类中的重载函数,然后按照我们自己定制的函数体来完成我们的需求;下面是一个链表类的例子:

struct ListNode{
	ListNode* _next;
	ListNode* _prev;
	int _data;
	//专属类重载
	void* operator new(size_t n){
		void* p = nullptr;
		p = allocator<ListNode>().allocate(1);
		cout << "memory pool allocate" << endl;
		return p;
	}
	void operator delete(void* p){
		allocator<ListNode>().deallocate((ListNode*)p, 1);
		cout << "memory pool deallocate" << endl;
	}
};
class List{
public:
	List(){
		_head = new ListNode;
		_head->_next = _head;
		_head->_prev = _head;
	}
	~List()
	{
		ListNode* cur = _head->_next;
		while (cur != _head)
		{
			ListNode* next = cur->_next;
			delete cur;
			cur = next;
		}
		delete _head;
		_head = nullptr;
	}
private:
	ListNode* _head;
};
int main(){
	List l;
	return 0;
}

newdelete实现原理

内置类型

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

自定义类型
  • new的原理
    • 调用operator new函数申请空间;
    • 申请空间完毕后,为其调用构造函数进行初始化;
  • delete的原理
    • 在空间上执行析构函数,完成对象中资源的清理工作;
    • 调用operator delete函数释放对象的空间;
  • new T[N]的原理
    • 调用operator new[]函数,在operator new[]函数中实际调用operator new函数完成 N 个对象空间的申请;
    • 在申请的空间上执行 N 次构造函数;
  • delete[]的原理
    • 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理;
    • 调用operator delete[]释放资源空间,实际在operator delete[]函数中调用operator delete来释放空间;

定位new表达式

  • 概念:定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象;
  • 语法格式:new (place_address) type或者new (place_address) type(initializer-list),这其中place_address必须是一个指针,initializer-list是类型的初始化列表;
  • 使用场景:定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化;

小结

malloc / freenew / delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. mallocfree是函数,newdelete是操作符,因此在使用前者时需要包含对应的头文件,而使用后者则不用;
  2. malloc申请的空间不会初始化,new可以初始化;
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可;
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型;
  5. malloc申请空间失败时,返回的是 NULL,因此使用时必须判空,new不需要,但是new需要捕获异常;
  6. 申请自定义类型对象时,malloc / free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值