​malloc​​​和​​new​​的区别

  1. ​malloc​​​是按字节开辟空间的,​​new​​开辟内存时需要指定类型(​​new int()​​),​​malloc​​开辟内存返回的都是​​void*​​,而​​new​​返回的是对应类型的指针
  2. ​malloc​​​负责开辟空间,​​new​​不仅有​​malloc​​的功能,还可以进行数据初始化和构造对象,比如:​​new int(10)​​。​​new​​有开辟空间和构造的功能。
  3. ​malloc​​​开辟内存失败返回​​nullptr​​,而​​new​​则会抛出​​bad_alloc​​异常
  4. 我们调用​​new​​实际上是调用的​​operator new​​,可以重载​​operator new​

​free​​​和​​delete​​的区别

  1. ​delete​​​先调用析构函数,再释放空间(即​​free​​)
  2. 我们调用​​delete​​实际上是调用的​​operator delete​​,可以重载​​operator delete​

我们先重载一下new、delete,添加测试类

// new实际上先调用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实际上先调用对象的析构函数,然后调用operator delete回收内存空间,
void operator delete(void* ptr) {
cout << "operator free addr:" << ptr << endl;
free(ptr);
}

// 用于数组
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 free[] addr:" << ptr << endl;
free(ptr);
}

class Test {
public:
Test(int data = 10)
:ma(data)
{
cout << "Test()" << endl;
}
~Test() {
cout << "~Test()" << endl;
}

private:
int ma;
};

存在自定义构造析构函数,new[]

int main() {
try {
Test* p = new Test[5];
cout << "开辟空间后返回的地址:" << p << endl;
delete[] p; // 调用析构,不会查看前4个字节确定数组元素的个数
// delete[]p; //会查看前4个字节确定数组元素的个数
}
catch (const bad_alloc& err) {
cerr << err.what() << endl;
}
return 0;
}

理解malloc、free、new、delete_析构函数


可以看到,我们要分配5个Test对象的空间,这5个对象的空间为20字节,而size却是24,还多出了4字节空间。是的,malloc就是需要多分配4字节空间用于存储对象的个数,以便delete[]执行指定次数析构函数

delete正确执行析构函数了,还要释放空间,即free,那free怎么知道要释放多少空间?

malloc会多分配4字节空间用于存放5个Test对象的个数,然后返回开辟空间的地址,在这个地址之前还有8字节,叫做块头,存放了真正给用户动态开辟的空间大小,也就是24

delete[]在释放空间的时候,会在用户传入地址往前查看4字节,执行指定次数析构函数后再执行free,free会在这24字节空间再往前偏移8字节,查看需要释放的内存大小(24),连同块头的8字节一起释放

理解malloc、free、new、delete_数组元素_02

当前场景下是内存这样,而为了防止病毒任意修改内存信息,某些平台可能会做一定的优化,比如在块头和分配的内存之间存在的填充

无自定义析构函数,new[]

测试类如下:

class Test {
public:
Test(int data = 10)
:ma(data)
{
cout << "Test()" << endl;
}

private:
int ma;
};

测试代码如下:

int main() {
try {
Test* p = new Test[5];
cout << "开辟空间后返回的地址:" << p << endl;
delete[] p; // 调用析构,不会查看前4个字节确定数组元素的个数
// delete[]p; //会查看前4个字节确定数组元素的个数
}
catch (const bad_alloc& err) {
cerr << err.what() << endl;
}
return 0;
}

理解malloc、free、new、delete_析构函数_03


此时传入new[]的size变成了20,少了我们之前用于存储对象个数的4字节空间

理解malloc、free、new、delete_析构函数_04


对于内置的数据类型和没有自定义析构函数的类类型,new和new[]分配空间的时候,不会额外分配4字节存储对象的个数,依然会存在8字节的块头。只要是不额外分配4字节存储对象的个数,new和new[]、delete和delete[]混用不会出错

开辟方式

释放方式

结果

new

delete

成功

new[]

delete

内嵌类型和不提供自定义析构函数成功;提供自定义析构函数失败

new

delete[]

内嵌类型和不提供自定义析构函数成功;提供自定义析构函数失败

new[]

delete[]

成功

对于提供自定义析构函数的类类型,用new[]开辟空间,用delete释放空间出错。new[]会开辟4字节存储对象的个数,执行delete时,直接往前偏移8字节获取开辟的内存大小,由于存在4字节内存存储对象的个数,实际需要偏移12字节才能获取块头信息进行空间正确释放。此外,只执行可一个对象的析构函数,而new[]构造了一个对象数组,这也不正确。

同理,对于提供自定义析构函数的类类型,用new开辟空间,用delete[]释放空间也出错。因为new没有开辟4字节存储对象个数,而delete以为存在这4字节,执行析构函数的次数就从块头获取了,这不正确。想要访问块头,执行delete[]时会偏移12字节,越过了块头,这也会出问题。

参考:​​为什么new/delete和new[]/delete[]必须配对使用?​