文章目录
new的使用
new和malloc的作用一样,都是从堆上申请一块空间。使用malloc时,需要指定申请空间的字节大小
int* p = (int*)malloc(sizeof(int) * 40);//比如开辟40个int的空间
使用起来很麻烦,不仅字节大小要用sizeof,对于返回的地址还要强制类型转换。相比之下new的使用就更加简洁
int main()
{
int* p1 = new int;// new一个int对象
int* p2 = new int[10];// new十个int对象
int* p3 = new int(1);// new一个int对象并初始化成1
int* p4 = new int[10]{ 1,2,3 };// new十个int对象,并初始化成括号里的内容
return 0;
}
相应的,如果申请了多个对象的空间,释放时也要对所有空间进行释放
int main()
{
int* p1 = new int;// new一个int对象
int* p2 = new int[10];// new十个int对象
int* p3 = new int(1);// new一个int对象并初始化成1
int* p4 = new int[10]{ 1,2,3 };// new十个int对象,并初始化成括号里的内容
delete p1;// 释放一个对象的空间
delete[] p2;// 释放多个对象的空间
delete p3;
delete[] p4;
return 0;
}
如果申请了多个对象,但只释放一个对象,很可能导致内存泄漏。所以动态内存的申请与释放要对应的使用。
如果new char[100],但使用delete而不是delete[],delete只释放一次内存(调用一次自定义类型的析构函数),这样可能导致程序的崩溃。
与C语言的malloc的差别
通过调试对比两者区别
class Stack
{
public:
Stack(int capacity = 4)
{
_data = new int[capacity];
_capacity = capacity;
_top = 0;
}
~Stack()
{
delete[] _data;
_capacity = 0;
_top = 0;
}
private:
int* _data;
int _capacity;
int _top;
};
int main()
{
Stack* ps1 = new Stack;
Stack* ps2 = (Stack*)malloc(sizeof(Stack));
if (ps2 == nullptr)
exit(-1);
delete ps1;
free(ps2);
return 0;
}
首先Stack中的_data是用来存放数据的,并且是new出来的空间。
可以看到new出来的Stack(ps1指针)的_capacity和_top都进行了初始化,而malloc出来的Stack(ps2指针)没有进行初始化,都是随机值。原因是:当new的对象类型是内置类型时,malloc和new没有区别,但new一个自定义类型时,new不仅会开辟空间,还会调用该类型的构造函数(也就是将new出来的对象进行了初始化),而malloc只负责开空间,初始化需要我们自己写。
对于空间的释放,delete会先调用Stack的析构函数,再去释放ps1指针,而free会直接释放ps2指针。
但Stack中的_data还开辟了空间,如果直接释放ps2,_data指向的空间就无法释放,此时出现内存泄漏。所以使用delete更省事,只要类型的析构函数正确的释放成员变量申请的空间,用delete释放内存就一行代码的事,但用free的话,要先去释放_data(类的成员变量申请的空间,但该成员如果是私有的则不能直接访问),再去释放ps2。显然在C++使用new和delete是更爽的,编译器为我们做的事实在是太多了。
(还有一个区别,new和delete是操作符,而malloc和free是函数)
new和delete的底层原理
operator new和operator delete
new和delete是C++中的操作符,operator new和operator delete是C++中的全局函数,new在底层会调用operator new,delete同理。
以下的代码是operator new和operator delete在库中的实现
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: 该函数最终是通过free来释放空间的
*/
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;
}
可以看出operator new在底层调用了malloc(C++利用现有的接口malloc,而不是重新造轮子),operator new实际是malloc的封装,而malloc申请失败,会返回nullptr,但operator new不会返回空指针,而是抛异常,malloc申请成功,函数返回申请空间的地址。
这就是new的调用过程,delete同理:delete去调用operator delete,operator delete调用free。所以new的实现是1.调用operator new2.调用构造函数。delete的实现是:1.调用operator delete2.调用析构函数
重载专属的operator new
如果有这样的场景:需要多次new对象,new又去调用operator new,而operator new调用的是malloc,malloc从堆上申请空间,每次都从堆上申请空间,这样的成本太大,效率太低。因此可以重载operator new,使operator new从内存池上申请空间,改变operator的底层实现机制,从而节省成本,提高效率(库中有内存池,可以使用)。
定位new
定位new是在已经分配的内存空间中调用构造函数去初始化一个对象。
如果有这样的场景,operator new了一个Stack,但Stack有_data,operator new不会为_data申请空间(_data的内存空间没有分配),并且_data是私有成员不能直接访问,需要调用Stack的构造函数去申请_data的空间,但构造函数不能显式的调用,构造函数只会在对象定义时被编译器自动调用。但使用定位new可以间接的调用构造函数
使用:new(指针名,指向已经分配过内存的空间)要调用构造函数的类型名(初始化列表)
ps1是指向Stack的指针,并且Stack的空间已经被operator new分配过了,要调用Stack的构造函数,所以接下来写的是Stack,最后的调用Stack的构造函数要传的参数,如果该构造函数无参,可以不写。
malloc和new的区别
关于两者的区别,从两个角度上分析,一个是用法,一个是底层实现。
1.malloc是函数,但new是操作符
2.使用malloc需要指定开辟空间的大小,new只要跟上要开辟空间的类型
3.使用malloc需要对返回的指针进行强转,new则不需要
4.对于自定义类型,malloc不会对其进行初始化,而new会去调用自定义类型的构造函数进行初始化(delete也是一样,会调用对象的析构函数进行资源的清理,free只会释放对象)
5.malloc申请失败会返回空,需要进行检查,而new申请失败会抛异常