今天就浅谈一下malloc/free和new/delete的区别,剖析一下它们的工作原理以及使用宏来模拟实现new/delete的功能。
有了 malloc/free 为什么还要 new/delete ?
malloc 与 free 是 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free,因此 C++ 语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete 。
区别:
①malloc/free是c/c++的标准库函数,而new/delete则是c++中的运算符;
②malloc/free只是动态分配内存空间/释放空间。而new/delete除了这些以外还会调用类的构造函数和析构函数进行初始化和清理;
③malloc/free需要手动计算类型大小,且返回值为void*,new/delete则可以自己计算类型大小并且返回对应类型的指针。
④申请的内存所在位置:malloc在堆上动态分配内存,new在自由存储区为对象动态分配内存;
注意:从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。我们所需要记住的就是:堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。(关于自由存储区和堆的区别可以参考一本书:exceptional C++)
⑤:动态内存分配失败时的返回值:new内存分配失败时,会抛出bac_alloc异常,不会返回NULL;malloc分配内存失败时会返回NULL;
底层实现:
在使用new的时候做了两件事:
1、调用operator new分配空间
2、调用构造函数初始化对象
在使用delete的时候也做了两件事:
1、调用析构函数清理对象
2、调用operator delete函数释放空间
在使用new[N]的时候也做了两件事:
1、调用operator new分配空间
2、调用N次构造函数初始化N个对象
在使用delete[]的时候也做了两件事:
1、调用N次析构函数清理N个对象
2、调用operator delete函数释放空间
operator new/operator delete operator new[]/operator delete[] 和 malloc/free用法一样。他们只负责分配空间/释放空间,不会调用对象构造函数/析构函数来初始化/清理对象。实际operator new和operator delete只是malloc和free的一层封装。
我们要思考的是在调用delete[ ]时,编译器是怎么知道要调用多少次析构函数来清理对象。
其实在使用new[ ]创建多个对象时,编译器会多开辟四个字节的空间用来存储对象的个数,因此在使用delete[ ]时编译器将会获得要清理的对象个数。(注意:当使用delete [ ]时,我们必须显示定义析构函数,否则会造成内存泄漏)
我们以一个日期类为例子:
new [ ]
delete [ ](一定要显示定义析构函数,否则可能会造成内存泄露)
为什么malloc/free、new/delete和new[]/delete[]要匹配使用?
#include<iostream>
using namespace std;
class Array
{
public:
Array(size_t size = 10)
: _size(size)
, _a(0)
{
cout << "Array(size_t size)" << endl;
if (_size > 0)
{
_a = new int[size];
}
}
~Array()//显示的定义了析构函数
{
cout << "~Array()" << endl;
if (_a)
{
delete[] _a;
_a = 0;
_size = 0;
}
}
private:
int* _a;
size_t _size;
};
这里我们只看看使用的情况:(仅对自定义类型不考虑内置类型)
Array* p1=new Array;
①delete p1;//程序正常
②delete [] p1;//程序崩溃,delete会在p1-4的地址处释放空间造成程序崩溃
③free(p1);//会造成内存泄漏,free不会调用析构函数
Array* p2=new
Array[10];
①delete p1;//程序不仅内存泄漏(仅对第一个对象调用了析构函数),还会崩溃(释放空间的地址不对,在起始地址+4的地址处释放造成程序崩溃)
②delete [] p1;//程序正常
③free(p1);//程序崩溃,内存泄漏
注意:对于内置类型而言,由于释放空间不需要调用析构函数,因此,也就不会多开四个字节,也就是说:delete和delete[]是相同的效果(尽管如此,还是建议大家要匹配使用)。
实现NEW_ARRAY/DELETE_ARRAY宏,模拟new[]/delete[]申请和释放数组:
#define NEW_ARRAY(PTR,TYPE,N) \
do \
{
PTR = (TYPE *) operator new(sizeof(TYPE)*N + 4); \
*(int*)PTR = N; \
PTR = (TYPE *)((char*(PTR)+4)); \
for (size_t i = 0; i < N; i++)
new(PTR + i)TYPE;
}while (false);
#define DELETE_ARRAY(PTR,TYPE) \
do \
{
size_t N = *((int*)PTR-1);
for (size_t i = 0; i < N; i++)
PTR[i].~TYPE();
PTR = (TYPE*)((char*)PTR - 4); \
operator delete PTR; \
}while (false);