在C++中new
与delete
即是关键字也是一种特殊的运算符。在C++程序中的内存管理主要通过关键字new/delete
和new[]/delete[]
实现。
一、new和delete的作用
其实,new和delete底层也是通过调用malloc和free来实现的。
除了对内存的空间的申请,new
还要负责调用相应的构造函数对类对象进行初始化。 所以关键字new
在整个调用过程中完成的工作主要是1.首先通过调用operator new
分配了指定大小的未被初始化的空间,2.此后再调用构造函数对类对象所在内存空间进行初始化,3.最后返回新分配并构造好的对象的指针 。 new
本身并不直接开辟内存。
而delete
则是 1.调用指针所指向的对象的析构函数,对打开的文件进行关闭,2.释放指针所指向空间的内存。
这其中的内存申请和释放的底层实现还是通过malloc
和free
来实现的。 同理new[]
和delete[]
会对对象数据中的每一个对象进行对象的空间的分配和销毁。
operator new 和operatoe delete函数原型如下:
void* operator new(size_t size);
void* operator new[](size_t size);
void operator delete(void *p);
void operator delete[](void *p);
下面是operator new和operator delete的简易实现:
下面有几点很重要:
-
由于内部数据类型的“对象”没有构造与析构的过程,对其使用
malloc/free
或者new/delete
是等价的。 -
对于
new[]
分配的对象数组,最后返回的指针与其内部通过operator new[]
中返回的指针相差4个字节,多出的四字节空间用于存储对象数组的对象个数。 因此在通过delete[]
释放内存时,不用指定对象个数,只需要将new[]
返回的指针前移四个字节就可以了。 -
free
或者delete
只会释放指针指向空间的内存,但不会对指针本身做任何处理,所以为了防止野指针,在释放了内存后要及时将指针置为NULL。(这个地方其实有点争议,不过为了防止double free,在知乎上有很多讨论,我也没想到更好的办法。所以,我的建议就是统统用智能指针)
int *p = new int[10];
delete p;
以上代码中new[]
首先调用函数operator new[]
分配了足够的内存大小(需要多出四个字节存放数组的大小),然后在刚分配的内存上调用构造函数初始化对象,最后返回对象数组的指针(不是分配内存空间的首地址,因为首地址存放的是数组的大小,返回地址即内存首地址+4的地址)。delete
只完成了首先调用一次p指向的对象的析构函数,然后调用operator delete(p)释放内存。因此由于只对第一个元素调用了析构函数而另外9一个对象并没有析构,这有可能会造成内存泄漏。此外因为分配内存的首地址是p所指地址-4位置,所以直接释放p指向的空间会引起段错误。
二、new/delete/malloc/free常见问题解析
1、new和free,malloc和delete是否可以互相调用。
对于内部数据类型,由于其“对象”没有构造与析构的过程,对其使用malloc/free
或者new/delete
是等价的。
2、能否直观地重新分配内存
使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。
3、连续两次调用free会怎么样
无论是free()还是delete().如果连续两次free()或delete(),则程序在编译时不会报错,但是在运行时会报错。 free(p):对应malloc(),一旦malloc()一块内存,则相当于机器吧这块内存借给你了,你可以随便使用这块内存,机器就不会再把这块内存借给其他程序,所以其他程序就不会使用这块内存。而一旦free()后,相当于你把这块内存还给了机器,机器就可以把这块内存借给其他程序了。free()相当于“还”,第一次free()时,这块内存是被已经借出来给你了,你可以“还”,但是第二次free()时,这块内存是属于机器的,你拿着机器的内存还给机器,肯定会出错(如果在两次free()之间又有线程被分配了这块地址,则应该不会报错吧)。虽然你把这块内存还给了机器,但是指针p还是指向了这个地址,要把他=null.否则就变成了野指针,他扔可以操作刚才的地址,但是这个地址是不应该被冲走做的,如果操作可能就破坏了其他的使用这个内存的程序的数据,是非常危险的,叫double free漏洞。
4、是否可以被重载
opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:
5、malloc和new分别在哪里分配内存
new操作符从自由存储区(free store) 上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。