c++动态内存管理

c/c++内存分布

虚拟地址空间分布
在这里插入图片描述

  1. 栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共 享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量。
为什么操作系统要划分这些区段

答:为了找数据,存数据方便

以下的代码分别在虚拟地址空间中的哪个段
 int globalVar = 1; 	//数据段
 static int staticGlobalVar = 1; 	//数据段
 void Test() 
 {    
 static int staticVar = 1;   //数据端
  int localVar = 1;      //栈
  int num1[10] = {1, 2, 3, 4};  //栈
  char char2[] = "abcd";    //char数组在栈,里面存储的abcd字符串常量在代码段
  char* pChar3 = "abcd";    //pChar指针在栈上,里面的abcd字符串常量在代码段
  int* ptr1 = (int*)malloc(sizeof (int)*4);   //指针在栈上,分配的空间在堆上
  int* ptr2 = (int*)calloc(4, sizeof(int));   //指针在栈上,分配的空间在堆上
  int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);   //指针在栈上,分配的空间在堆上
  free (ptr1);    
  free (ptr3);
 }

sizeof与strlen的区别

  1. sizeof()是运算符,strlen()是库函数
  2. sizeof()在编译时计算好了,strlen()在运行时计算
  3. sizeof()计算出对象使用的最大字节数,strlen()计算字符串的实际长度
  4. sizeof()的参数类型多样化(数组,指针,对象,函数都可以),strlen()的参数必须是字符型指针(传入数组时自动退化为指针)

c语言动态开辟空间

动态申请空间的方式
  1. malloc void * malloc ( size_t size );
  2. callocvoid * calloc ( size_t num, size_t size );
  3. reallocvoid * realloc ( void * ptr, size_t size );

相同点:

  • 都是从堆上开辟空间
  • 返回值类型都是void*
  • 申请空间失败,返回的都是NULL
  • 申请的空间都需要手动释放
  • 在接收返回值时都需要进行强制类型转换

不同点

malloc:

  • 参数:字节数,
  • 不会对申请的空间进行初始化,
  • 在接收返回值时必须强制类型转换
  • 如果申请空间失败,返回值NULL,使用前必须判断是否为空

calloc:

  • 参数列表不同:void * calloc ( size_t num, size_t size );,第一个为元素个数,第二个为单个元素大小
  • 函数功能差异:calloc会将申请的空间初始化为0

realloc

  • 参数列表:void * realloc ( void * ptr, size_t size );
  • 函数功能:将ptr所指向的空间调整到size字节,如果ptr为空----->函数行为与malloc相同
  • ptr非空,判断是缩小还是增大,缩小的话,对原空间进行调整,最后返回该空间的首地址,扩大的话,有两种情况:1. 扩大一点,2.扩大很多
1.如果当前空间后面有足够的空间能够支持size字节的话,直接扩容
2.如果没有足够的空间,realloc做四件事情,第一:申请新空间,第二:拷贝元素,第三:释放旧空间,第四:返回新空间的地址
动态释放方式

手动free

malloc

int* p =(int*)malloc(10*sizeof(int));

40字节-----堆上,只要把空间分配给一个应用程序,那么其他应用程序就用不了。系统必须对malloc申请的空间进行管理。
一般malloc申请空间时,比如申请40个字节,除了申请的40字节空间,他还会在前面加一个结构体来管理40字节的空间,在后面加4个字节的空间用来防止越界
在这里插入图片描述

详解c中动态内存分配

C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出 了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

new申请内置类型
int main()
{
	//new 申请单个类型元素的空间----默认情况下new出的空间在堆上

	int *p1 = new int;
	int *p2 = new int(10);
	int *p3 = new int[10];
	int *p4 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;
	return 0;
}
  1. newdelete不是函数,c++提供的新的操作符
  2. new/new[] 只需在其后跟空间的类型,不需要传递字节数
  3. new后跟的就是空间的类型,不需要强制类型转换
  4. new/new[]可以进行初始化
  5. new的结果不需要判空
    注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[]
如果没有匹配

如果申请的是内置类型的空间,没有匹配的话,不会产生任何后果

void Test()
{
	int *p1 = (int *)malloc(sizeof(int)* 4);
	int *p2 = (int *)malloc(sizeof(int)* 4);

	delete p1;
	delete p2;

	int *p3 = new int;
	int *p4 = new int;
	free(p3);
	delete[]p4;

	int *p5 = new int[10];
	int *p6 = new int[10];
	free(p5);
	delete p6;
}
new申请自定义类型数据

未进行匹配使用

//自定义类型
class Test
{
public:
	Test()
	{
		_data = 10;
		cout << "Test():" << this <<endl;
	}
	~Test()
	{
		cout << "~Test()" << this << endl;
	}
private:
	int _data;

};

void Test2()
{
	Test *p1 = (Test *)malloc(sizeof(Test)* 4);
	//Test *p2 = (Test *)malloc(sizeof(Test)* 4);

	delete p1;
	//delete[] p2;

	Test *p3 = new Test;
	Test *p4 = new Test;
	free(p3);
	delete[]p4;

	Test *p5 = new Test[10];
	Test *p6 = new Test[10];
	free(p5);
	delete p6;
}
  1. 对于自定义类型,只要设计到[]不匹配,肯定崩溃
  2. new会调用构造函数,而free不会调用析构函数----对象中的资源不会被销毁—内存泄露
  3. malloc申请空间时不会调用构造函数—申请的是与对象大小相同的一块内存空间,不能将该块内存空间看成是一个对象
  4. delete需要调用析构函数

operator new与operator delete函数

new是先调用构造函数,还是先申请空间?

第一件事情:申请内存空间,第二才是调用构造函数,完成对象的初始化
在这里插入图片描述
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的 全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局 函数来释放空间。

/* operator new:该函数实际通过malloc来申请空间,
当malloc申请空间成功时直接返回;申请空间失败,尝试 执行空间不足应对措施,
如果改应对措施用户设置了,则继续申请,否则抛异常。 */ 
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); 
}

循环调用malloc来不断申请空间,直到申请成功,

  1. malloc申请----->成功----->直接返回
  2. malloc申请------>失败(内存空间不足)------->检测是否提供空间不足的应对措施,提供:继续申请,未提供:抛异常
/* operator delete: 该函数最终是通过free来释放空间的 */ 
void operator delete(void *pUserData) 
{        
		//_CrtMemBlockHeader  这个就是malloc申请内存时前面的哪个结构体
		_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; 
 }
 

delete p;

  1. 调用析构函数释放对象中的资源-----对象中的资源
  2. 调用void operator delete(void *p) 释放对象本身的空间
总结

operator new 实际也是通过malloc来申请空间,如果malloc申请空间 成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异 常。operator delete 最终是通过free来释放空间的

void* operator new(size_t size,const char* file,const char* funname,size_t line)
{
	cout << file << "-" << funname << "-" << line << "-" << size << endl;
	return malloc(size);
}

void operator delete(void *p, const char* file, const char* funname, size_t line)
{
	cout << file << "-" << funname << "-" << line << "-" << endl;
	free(p);
}
#define new new(__FILE__, __FUNCDNAME__, __LINE__)

int main()
{
	int *p = new int;
	delete p;
	system("pause");
	return 0;
}

new和delete的实现原理

内置类型

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

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

内存碎片化

如果每回一小块一小块申请内存,就会造成内存的浪费。所以c++有一个现成的内存池就是空间配置器
以下代码对链表的节点ListNode通过重载类专属 operator new/ operator 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;
};

定位new表达式

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

class Test 
{
public:   
	Test() : _data(0)   
	{ cout << "Test():" << this << endl; }

		 
	~Test()   
	{ cout << "~Test():" << this << endl; }   
private:   
	int _data;
};
int main()
{
	Test* pt = (Test*)malloc(sizeof(Test));
	new(pt)Test;

	//delete pt;
	pt->~Test();
	system("pause");
	return 0;
}

关于c++动态管理方面的题目

题目总结的链接

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值