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

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);    //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);保证宏的一体性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值