首先明确,new、delete是C++里的运算符,和加减乘除一个类别;而malloc、free是C中的函数。
1、new 与 malloc的区别
执行内容
malloc,是按字节来分配内存的(意思是分配的大小,由自己指定),返回类型是void* ,因此在使用的时候,需要强制转换类型;
new,是先开辟内存(由编译器根据你的对象类型,来决定开辟内存的大小),然后对内存进行初始化(执行构造函数),返回正确指向数据的指针。
返回类型
malloc,返回的是void* 的指针,如果申请内存失败,返回nullptr指针;
new,返回的是申请对象的指针,如果申请内存失败,抛出 bad_alloc类型的异常;因此两者判断是否开辟成功的所做也不一样。
int main()
{
int *p = (int*)malloc(sizeof(int));
if(p == nullptr) {
return 0;
}
.....
free(p);//为了避免野指针的存在,使用完之后给p赋值为nullptr;
int* p = new(int(10))
//if (p == nullptr) 这样判断是错误的
try {
int* p = new(int(10));
delete p;//为了避免野指针的存在,使用完之后给p赋值为nullptr;
}catch (bad_alloc){
}
return 0;
}
根据上面两个的分析,然后就可以自己来判断 new,malloc 的其他区别,下面可以自行分析
2、delete 与 free的区别
执行过程
delete:先调用析构函数,然后释放内存; free 只释放内存。
根据delete的工作步骤,如果是针对于内置数据类型,例如 int,float等,new并不需要执行构造函数;delete并不需要执行析构函数,因此和malloc,free此时并没有什么区别。
3、new 内在实现
使用关键字 new 在堆上动态创建一个对象A时,A* p = new A() ,它实际上做了三件事:
1.向堆上申请一块内存空间(编译器生成)( operator new )
2.调用构造函数 (调用A的构造函数(如果A有的话))( placement new)
3.返回正确的指针,即申请数据类型的指针,A*。
operator new 和operator new[]功能都是仅仅分配内存,底层调用了 malloc 函数。 operator new是给new用的,operator new[]是给new[]用的。而且可以被重载。
void* operator new(size_t size) {
void* p = malloc(size);
if (p == nullptr) {
throw bad_alloc();
}
return p;
}
void* operator new[](size_t size) {
void* p = malloc(size);
if (p == nullptr) {
throw bad_alloc();
}
return p;
}
4、delete 内在实现
使用 delete 的时候,也是一样的,其行为如下:
1、定位到指针p所指向的内存空间,然后根据其类型,调用其自带的析构函数(内置类型不用)
2、然后释放其内存空间(调用 operator delete ,将这块内存空间标志为可用,然后还给操作系统)
3、在vs上测试,编译器并没有将申请的指针指向nullptr,因此此时指针为野指针,需要自己手动赋值为nullptr。
void operator delete[](void* ptr) {
free(ptr);
}
void operator delete(void *ptr) {
free(ptr);
}
5、何时需要配套使用
1、可以不配套
原则上都需要配套使用,但是当 new 的对象是基本数据类型,没有构造函数、析构函数时候,new 和 delete的实现和 malloc、free并没有什么区别,不配套使用,也不会报错。
int main()
{
int* p = new int(10);
delete[] p;
int* p = new int[10];
delete p;
return 0;
}
2、必须配套
当存在构造函数和析构函数时候,必须配套使用。
class Test{
public:
Test(int data = 10) : mdata(data) {}
~Test(){}
private:
int mdata;
}
Test* ptr = new Test[5];
delete[] ptr;
当编译器开辟内存的时候,看见是要开辟的对象的数组,开辟的内存不只只是5*4个字节,还会在多加4个字节,表示一共有多少个对象。因此,在delete[] 的时候,编译器才知道要对几个对象进行析构。
Test* ptr = new Test();
delete[] ptr;
如果像上面这样操作的话,在delete[] ptr的时候,编译器看见 [], 就会像上找四个字节,此时的地址就不再是对象的地址了,因此产生错误。
Test* ptr = new Test[5];
delete ptr;//只会执行一次析构函数
同样道理,new[] delete也是,delete只会执行一次,只delete掉了ptr指向的那一个,后面的没法执行。
6、析构函数里要不要写delete
class A{
public:
A(int data) : mdata(data){}
~A(){};
private:
int data;
};
//~A(){ cout << "析构" << endl; }
class A1{
public:
A(int data) : mptr(new int[10]) {}
~A() {
delete[] mptr;
}
private:
int* mptr;
};
对象A析构函数,不需要写 delete,因为此时并没有在其他地方再次开辟内存;
对象A1析构函数,需要继续写 delete(同时注意配套),因此此时,在对象A1中,还存在在其他地方继续开辟内存,所以delete的时候,要把都清理干净。(嵌套delete)
7、从内存中看 new new[]
在使用规范上,一定要遵守new delete 与 new[] delete[] 配套使用的规范,虽然有时候,我们写的代码可能不会报错,但终归看起来奇奇怪怪,甚至造成内存泄漏。
上面的例子中,当我们自定义数据类型,没有写析构函数的时候,地址上方的四个字节,也没有存储数组中元素的个数,
在自定义数据类型中,如果涉及到内存申请,一定得写析构函数,防止内存泄漏。当我们用delete new[]时候,只会执行第一个元素的析构函数,后面的便造成了内存泄漏。在多次实验后,感觉delete[] 主要是要知道要执行多少次析构函数。
也就是当delete 一个 new[] 的时候,也可以释放自定义数据类型占的内存,但自定义数据类型中存在新开辟的堆空间,需要在析构函数执行,然后这部分没法执行。