1、malloc/free和new/delete
1、C语言中malloc/calloc/realloc
(1)malloc
函数原型:void *malloc( size_t size );
使用范例:int* array_a = (int *)malloc(sizeof(int)*10);//40 byte
说明:malloc动态开辟指定字节数的连续空间,返回void*
需要强转使用,开辟的空间并不初始化。如果开辟失败返回一个NULL指针。
(2)calloc
函数原型:void *calloc( size_t num, size_t size );
使用范例:int* array_b = (int *)calloc(10,sizeof(int));//40 byte
说明:calloc跟malloc的区别,一个是开辟空间大小计算方式不一样,另一个是calloc会初始化空间为0
(3)realloc
函数原型:void *realloc( void *memblock, size_t size );
使用范例:int* array_a = (int *)realloc(array_a, sizeof(int));//40+4 byte
说明:realloc是在已开辟的空间向后增加空间,第二个参数是希望增大的字节数。若内存中有足够大的size的连续空间,那么就在当前空间的后面开辟。但是,不够大,realloc会重新开辟一块空间,再将原来的数据拷贝,同时将原空间释放。如果第一个指针参数是NULL,此时realloc的作用就跟malloc。
(4)free
函数原型:void free( void *memblock );
使用范例:free(array_a);
说明:free函数用于释放动态申请的空间,以避免因动态申请造成的内存泄漏。但不能对同一块空间释放多次,可以对指向NULL的指针进行free。一般free与malloc、calloc、realloc搭配使用。
2、C++中new/delete/new[ ]/delete[ ]
3、C语言的动态开辟与C++的动态开辟
- 两者都是动态开辟的入口
- new会自动计算类型的大小,同时返回指向该空间的同类型指针
- new申请失败会抛异常,malloc等会返回一个NULL指针
- new是一个操作符,malloc等是函数
- 对于自定义类型,new和delete分别会调用构造函数和析构函数
- new在动态申请空间的时候,可以显示的初始化。
2、new/delete、new[]/delete[]剖析
new/delete、new[]/delete[]应该搭配使用,否则有可能出现内存泄漏或者程序奔溃的现象。
1、四个函数
void* operator new(size_t size); //new
void operator delete(void* ptr); //delete
void* operator new[](size_t size); //new[]
void operator delete[](void* ptr); //delete[]
这四个函数不是 operator 重载,举例:
operator new 是 malloc 的C++封装,相比于malloc,动态申请失败,malloc返回的是NULL,这个函数抛出异常。这是OPP的处理方式。当我们调用new 到时候,系统会调用该函数,然后再调用构造函数。
2举例剖析
(1)一个问题
我们在调用delete[] 的时候,并没有传递个数,但是delete[]会调用N次析构函数,系统是如何知道?
举例如下:
class SeqList
{
public:
SeqList(size_t n = 3)
:_a(new int[n])
{
cout << "SeqList(size_t n = 3)" << endl;
}
~SeqList()
{
cout << "~SeqList()" << endl;
delete[] _a;
}
private:
int* _a;
};
int main()
{
SeqList* s1 = new SeqList[4];
delete[] s1;
return 0;
}
解析:
这个是因为对于自定义的类型,如果我们显示的调用了析构函数,new[ ]的调用会在开辟的空间之前多开辟一个空间,用于存放个数。当调用delete[ ]的时候,系统会向前读取这个值,从而知道调用几次析构函数。
(2)如果不是自定义类型或者不是显示的定义了析构函数,不会向前开辟多的空间
class AA//显示的调用析构函数
{
public:
AA()
{}
~AA()
{}
private:
size_t size;
};
class BB//未显示的调用了析构函数
{
public:
BB()
{}
private:
size_t size;
};
//调用
int* tmp = new int[10];
AA* aa = new AA[10];
BB* bb = new BB[10];
delete[] bb;
delete[] aa;
delete[] tmp;
以下几个组合也可以说明这个问题:
//1、AA是显示调用析构函数的自定义函数,会向前开辟一个空间
AA* p1 = new AA[10];
free(p1); //free不会向前取值,不会调用析构函数。系统奔溃。
delete p1; //delete不会向前取值,会调用一次析构函数。系统奔溃
//2、p1 用new开辟空间,不需要向前开辟空间
AA* p1 = new AA;
delete[] p1;//delete[]会向前索取空间,但是p1没有,系统崩溃
//2、BB没有显示的调用析构函数,不会向前开辟空间。
BB* p2 = new BB[10];
free(p2); //用free释放没有问题,而且没内存泄漏
delete[]p2;//虽然用delete[]会向前索取空间,但是因为BB没有显示调用析构函数,会使用缺省的析构函数,此时编译器优化,正常。
3宏模拟实现new[]/delete[]
(1)模拟new[]
#define NEW_ARRAY(ptr, type, n) \
do { \
ptr = (type*)operator new(sizeof(type)*n + 4); \
for (size_t i = 0; i < n; ++i) \
new(ptr + i)type; \
} while (); \
new(ptr + i)type;
是一个定位new表达式,他用于对已经分配好内存的指针,进行初始化。一般这个内存的获取是在内存池中。他的格式为new(指针)type
;
下面两者作用一致:
(2)模拟delete[]
#define DELETE_ARRAY(ptr, type) \
do { \
size_t n = *((int*)ptr - 1); \
for (size_t i = 0; i < n; ++i) \
(ptr + i)->~AA(); \
operator delete((char*)ptr - 4); \
} while ();
注意:使用宏定义多行代码的时候,最好使用换行符,但是换行符后面不能有空格。其次,一定要使用do{…}while(0);
保证宏的一体性。