小结 | C++中new/delete剖析及其宏模拟

来源:互联网 发布:tv霸网络电视下载 编辑:程序博客网 时间:2024/06/01 15:49

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++的动态开辟

  1. 两者都是动态开辟的入口
  2. new会自动计算类型的大小,同时返回指向该空间的同类型指针
  3. new申请失败会抛异常,malloc等会返回一个NULL指针
  4. new是一个操作符,malloc等是函数
  5. 对于自定义类型,new和delete分别会调用构造函数和析构函数
  6. new在动态申请空间的时候,可以显示的初始化。

这里写图片描述

2、new/delete、new[]/delete[]剖析

new/delete、new[]/delete[]应该搭配使用,否则有可能出现内存泄漏或者程序奔溃的现象。

1、四个函数

void* operator new(size_t size);    //newvoid operator delete(void* ptr);    //deletevoid* 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);保证宏的一体性。

原创粉丝点击