200-详解C++的new和delete

1、深入理解new和delete

  • new和delete称作运算符

200-详解C++的new和delete_第1张图片
我们转反汇编看看:
200-详解C++的new和delete_第2张图片
200-详解C++的new和delete_第3张图片
这2个运算符本质也是相应的运算符的重载的调用


  • 1、malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[10]
    所以malloc开辟内存返回的都是void*
    而new相当于运算符的重载函数 operator new ->返回值自动转成指定的类指针 int*
  • 2、malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化
new int(20);//初始化20  
new int[20]();//开辟数组是不支持初始化值的,但是支持写个空括号,表示给每个元素初始化为0 ,相当于每个元素调用int()成为0
  • 3、malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
    (也就是说,new运算符开辟内存失败,要把它的代码扩在try catch里面,是不能通过返回值和空指针比较的。)
	try//可能发生错误的代码放在try里面 
	{
		int *p = new int;
		delete []p;

		int *q = new int[10];
		delete q;
	}
	catch (const bad_alloc &err)//捕获相应类型的异常 
	{
		cerr << err.what() << endl;//打印错误 
	}

200-详解C++的new和delete_第4张图片

2、free和delete的区别?

delete p:

  • 调用析构函数,然后再free( p),相当于包含了free
  • 如果delete的是普通的指针,那么delete (int*)p和free( p)是没有区别的
  • 因为对于整型指针来说,没有析构函数,只剩下内存的释放
new ->operator new重载函数的调用 
delete ->operator delete重载函数的调用 

把new和delete的重载函数定义在全局的地方,这样我们整个项目工程中只有涉及到new和delete的地方都会调用到我们全局重写的new,delete的重载函数。

//先调用operator new开辟内存空间、然后调用对象的构造函数(初始化)
void* operator new(size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new addr:" << p << endl;
	return p;
}
//delete p; 先调用p指向对象的析构函数、再调用operator delete释放内存空间
void operator delete(void *ptr)
{
	cout << "operator delete addr:" << ptr << endl;
	free(ptr);
}

200-详解C++的new和delete_第5张图片

在这里插入图片描述
new和delete从内存管理的角度上来说和malloc和free没有什么区别

  • 就是内存开辟失败,返回值不一样

数组new和delete:

void* operator new[](size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr:" << p << endl;
	return p;
}
void operator delete[](void *ptr)
{
	cout << "operator delete[] addr:" << ptr << endl;
	free(ptr);
}

200-详解C++的new和delete_第6张图片
200-详解C++的new和delete_第7张图片
除非系统自己要实现一个内存池的方案,否则就规矩用库里提供的方案即可!


C++中,如何设计一个程序检测内存泄漏问题?

  • 内存泄漏就是new操作没有对应的delete,我们可以在全局重写上面这些函数,在new操作里面用映射表记录都有哪些内存被开辟过,delete的时候把相应的内存资源删除掉,new和delete都有对应关系
  • 如果整个系统运行完了,我们发现,映射表记录的一些内存还没有被释放,就存在内存泄漏了!
  • 我们用我们自定义的new和delete重载函数 接管整个应用的所有内存管理 ,对内存的开辟和释放都记录;也可以通过编译器既定的宏和API接口,把函数调用堆栈打印出来,到底在哪个源代码的哪一页的哪一行做了new操作没有delete

3、new和delete能混用吗?

C++为什么区分单个元素和数组的内存分配和释放呢?
下面这样操作是否可以???
200-详解C++的new和delete_第8张图片
200-详解C++的new和delete_第9张图片

void* operator new[](size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr:" << p << endl;
	return p;
}
void operator delete[](void *ptr)
{
	cout << "operator delete[] addr:" << ptr << endl;
	free(ptr);
}

  • 其实现在对于整型int来说,没有所谓的构造函数和析构函数可言,所以这样的代码就只剩下malloc和free的功能,所以底层调用的就是malloc和free。
  • 还是用的上面的 new/delete,以及数组new/delete
    200-详解C++的new和delete_第10张图片
    所以,它们现在混用是没有问题的!!!

那什么时候我们才需要考虑这些问题呢?
200-详解C++的new和delete_第11张图片
200-详解C++的new和delete_第12张图片


在这里面,我们new和delete能不能混用呢?
200-详解C++的new和delete_第13张图片
200-详解C++的new和delete_第14张图片
出现错误了。

此时new和delete不能进行混用了!

将上面的Test类修改:200-详解C++的new和delete_第15张图片
200-详解C++的new和delete_第16张图片
200-详解C++的new和delete_第17张图片

  • 先开辟空间;
  • 5次构造,5次析构;
  • 释放内存!

在这里,new和delete可以混用吗?
200-详解C++的new和delete_第18张图片

200-详解C++的new和delete_第19张图片
运行出错了。

我们最好是这样配对使用:
new delete
new[] delete[]

对于普通的编译器内置类型(int,float,double…)
new/delete[],new[]/delete,这样混用是可以的!
因为只涉及内存的开辟和释放(没有构造析构可言),底层调用的就是malloc和free。

但是,如果是对象,就不能混用了。

再次修改Test对象:

200-详解C++的new和delete_第20张图片

一个Test对象是4个字节。
每一个Test对象有1个整型的成员变量。

200-详解C++的new和delete_第21张图片

  • new的时候,分配了5个Test对象,但是不只是开辟了20个字节哦!
  • delete[]p2的时候先调用Test对象的析构函数,析构函数有this指针,this指针区分析构的对象,this指针把正确的对象的地址传到析构函数。

现在加了[]表示有好几个对象,有一个数组,里面的每个对象都要析构,但是它是怎么知道是有5个对象呢???


200-详解C++的new和delete_第22张图片

  • 多开辟了4个字节,存储对象的个数
  • 用户在写new Test[5]时,这个5是要被记录下来的。
  • 而且,new操作完了之后,给用户返回的p2指针指向的地址是0x104这个地址!即数组首元素的地址。
  • 并不是真真正正底层开辟的0x100这个地址,因为那个是不需要让用户知道的,用户只需要知道这个指针指向的是第一个元素对象的地址。
  • 当我们去delete[]p2的时候,它一看这个[]就知道释放的是一个对象数组,那么就要从p2(0x104)上移4个字节,去取对象的个数(int类型),知道是5个对象了(一个对象是4字节),然后把0x104下的内存平均分成5份,每一份内存的起始地址就是对象的起始地址,然后传给对象的析构函数,就可以进行对象的析构了。
  • 然后进行内存的释放,operator delete(从p2-4开始释放),从0x100开始释放!!!

200-详解C++的new和delete_第23张图片
200-详解C++的new和delete_第24张图片
这个代码错误在:

  • 实际上开辟的内存空间大小是20+4=24字节;
  • 开辟内存是从0028开辟的,因为它有析构函数,所以在底层给数组开辟内存时多开辟了4个字节来存储开辟的对象的个数;
  • 但是用户返回的是02c,比028刚好多了4个字节,也就是给用户返回的是真真正正对象的起始地址。

delete p2;它就认为p2只是指向1个对象,因为没有使用delete[],所以它就只是把Test[0]这个对象析构了而已,然后直接free(p2),从第一个对象的地址(02c)开始free,而底层内存是从028开始开辟的,应该从028开始释放。


我们换成delete[]p2,来运行看看:

在这里插入图片描述
200-详解C++的new和delete_第25张图片
从指针-4开始free释放内存的操作


200-详解C++的new和delete_第26张图片
这个代码的出错在:

  • 只是new出来1个对象,在0x104开辟的,p1也是指向了0x104;
  • 但是你用的是delete[],在delete[]的时候,认为是指向的是对象数组,因为还有析构函数,于是它就从0x104上移4个字节去取开辟对象的个数
  • 这就出现了问题了,这里只有一个Test对象!!!
  • 但是,由于delete[],关键是它free的时候,执行的是free(从0x104-4位置开始free)
  • 但是new的时候并不是从0x100开始开辟内存的。

200-详解C++的new和delete_第27张图片

自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟 对象数组 的时候,会多开辟4个字节,记录对象的个数

你可能感兴趣的:(C++/STL,c++,开发语言)