new和delete表达式
一、operator new 和 new operator的区别
new operator指的是new表达式,是一种操作,new操作,如stirng *pStr = new string("hello");
而operator new,这个new是一个运算符函数
前者是语言的特性,不能够改变,而后者是一个运算符函数,可以对其进行重载
二、operator delete 和 delete operator的区别
同样operator delete也是一个delete运算符函数,delete operator是一个delete表达式
delete运算符只是去释放内存,而delete表达式还干了其他事情,具体看四
三、new表达式工作步骤
当我们使用new表达式时,new做的事情
- 调用名为operator new的标准库函数,其作用是申请空间,即分配指定大的原始的未类型化的内存,用于保存指定类型的一个对象
- 再进行构造对象,运行该类型的一个构造函数初始化对象
- 再返回对象的指针,返回指向新分配并构造的构造函数对象的指针
四、delete表达式工作步骤
当使用delete表达式时,delete做的事情
- 调用析构函数,回收对象种数据成员所申请的资源
- 调用operator delete的标准库函数释放该对象所用的内存
例子:
#include <iostream>
#include <string.h>
using std::endl;
using std::cout;
class Computer {
public:
Computer(const char *brand, double price)
:_brand(new char[strlen(brand) + 1]()),
_price(price) {
strcpy(_brand, brand);
cout << "Computer(const char *, double)" << endl;
}
~Computer() {
delete [] _brand;
cout << "~Computer()" << endl;
}
private:
char *_brand;
double _price;
};
int main() {
Computer *pc = new Computer("lenovo", 8000);
printf("pc对象存放的地址:%p\n", &pc);
printf("pc对象所指内存的地址:%p\n", pc);
return 0;
}
当我们执行上面代码时,会发现创建了Computer对象,并分配了内存空间
Computer(const char *, double)
pc对象存放的地址:0x7ffd799f3840
pc对象所指内存的地址:0x56346e309e70
我们发现pc对象的地址在内存布局是偏高的,pc对象所指内存存放的地址是比其要低一些的,原因我使用的平台是Linux平台,内存分布是栈内存在高出向下生长,堆内存在低处向上生长,如此可见new申请了空间,还进行了对象的构造
但我们还发现一个问题,Computer对象的构造函数调用了,但为什么没有进行析构函数的调用?
这里就是我们要探讨的调用delete表达式时,其做的事情(见本节开头)
此时我们加上delete pc;
int main() {
Computer *pc = new Computer("lenovo", 8000);
printf("pc对象存放的地址:%p\n", &pc);
printf("pc对象所指内存的地址:%p\n", pc);
delete pc;
return 0;
}
Computer(const char *, double)
pc对象存放的地址:0x7fff74cceed0
pc对象所指内存的地址:0x560b87241e70
~Computer()
发现了析构函数的调用
这里我们为了验证内存布局,特意将数据成员设置成public方便我们使用,并用图来解释下列代码
Computer *pc = new Computer("lenovo", 8000);
printf("pc对象存放的地址:%p\n", &pc);
printf("pc对象所指内存的地址:%p\n", pc);
printf("pc中_brand的地址:%p\n", &pc->_brand);
printf("pc中_brand所指内存的地址:%p\n", pc->_brand);
delete pc;
Computer(const char *, double)
pc对象存放的地址:0x7ffdf7495ea0
pc对象所指内存的地址:0x560407d2be70
pc中_brand的地址:0x560407d2be70
pc中_brand所指内存的地址:0x560407d2be90
~Computer()
通过这张图和上面代码运行的结果我们可以发现:
- pc指针指向的Computer对象的首地址和_brand数据成员的地址一样,这表示了类的大小保存的是数据成员的大小(这里可以找相关类和对象的文章去看关于类的内存布局)
- _brand数据成员进行了深拷贝,就是在构造函数时
new char[strlen(brand) + 1]()
,申请了堆空间这个堆空间的地址就是pc中_brand所指内存的地址
- 从delete的工作机制中我们可以发现,第一步调用对象的析构函数,而我们的析构函数可以对其显式的定义出来,目的是为了释放对象中数据成员所申请的堆内存,即
delete [] _brand;
按照图中所进行的步骤为步骤1
- 然后再调用operator delete函数释放申请对象时所用的内存,即图中的
步骤2
问题
针对于delete的工作步骤,调用析构函数和对象销毁等价吗?
A:如果是内置对象(如int,long…)的话就等价,如果是自定义类类型的话就不等价,因为两步所执行的内容不一样。
六、定位new表达式
普通的new一般都会执行其步骤中的内容,主要一点是会申请内存空间,从堆中找到一个满足要求的内存块。
而定位new运算符(placement new),通过指定要使用的位置,即可以指定在哪块空间去“创建对象”,注意这里没有说是申请内存,而是说使用指定的位置去创建对象。
所以,定位new运算符,即指定位置之后,它并不负责去创建内存空间,也不负责检查所指定的地址是否可以使用。
与常规的new表达式不同,定位new不需要相应的delete去释放内存,原因是它本身就不开辟内存空间,它只是使用指定的位置去创建对象。
七、new/delete与malloc/free的相同点和不同点
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
}
}
void __CRTDECL operator delete(void* const block) noexcept
{
#ifdef _DEBUG
_free_dbg(block, _UNKNOWN_BLOCK);
#else
free(block);
#endif
}
这里抽取了new和delete运算符函数的源码,发现底层使用的是malloc和free
这里我们来总结一下malloc和new的相同点
不同点:
- malloc/free是C语言的标准库函数,而new/delete是C++的标准库函数
- malloc在使用该函数时就要把大小制定好(多少个字节),malloc申请的是原始的未初始化的堆空间;new能够自动分配空间大小,new申请的是已经初始化的堆空间
- new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小进行分配
- new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。
- new不仅分配一段内存,而且还会调用构造函数,malloc不会。
- new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。
- new是一个可重载的运算符,malloc是一个C标准库函数。
- new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。
- 申请数组时,new[]一次分配所有的内存,多次调用构造函数,搭配使用delete[],delete多次调用析构函数,销毁数组中的每个对象,而malloc则只能sizeof(int)*n;
相同点:
- 都是用来申请堆空间的
- malloc和free以及new与delete要成对出现,否则会造成内存泄漏