探究new和delete的秘密

简介:
  new 和 delete 是 C++ 用于管理 堆内存 的两个运算符, C 语言中的malloc 和 free是函数,而C++中new 和 delete 是运算符,malloc和new都属于动态的分配内存。


.有了malloc、free为什么还要有new、delete

  1. malloc和free是C++/C语言的标准库函数,new/delete是C++中的运算符,都可用于申请、释放内存。
  2. 对于非内部数据类型的对象而言,光有malloc和free无法满足动态对象的要求:对象在创建的同时要启动调用构造函数,对象在销毁的时候要自动调用析构函数,由于malloc和free是库函数不是运算符,不在编译控制权限内,不能把调用构造函数和析构函数的任务交给它们,因此C++语言需要一个能够完成【动态分配内存和初始化工作的运算符以及一个释放内存与清理的运算符,即new/delete】,即new/delete会调用构造函数、析构函数
  3. new传的是对象的个数,malloc传的是字节数。
  4. new/delete可以被重载

. 为什么malloc、free没能淘汰出局

  从上面我们看出了new/delete的功能完全包括了malloc/free,并且在其基础上还有改进,为什么C++不淘汰掉malloc/free?  

  1. C++程序兼容C的一切,所以C++有时会调用一些C程序,但是在C程序里动态的管理内存只能用malloc/free。
  2. 某些情况下,malloc/free可以提供比new/delete更高的效率。

. malloc、free使用:

  malloc/free

. new、delete的使用:

  敲黑板啦!敲黑板啦!敲黑板啦!
  先放使用例子:

int* p1=new int;//动态分配4个字节(1个int)的空间单个数据
int* p2=new int(3);//分配4个字节(1个int)并初始化为3
int* p3=new int[3];  //动态分配12个字节(3个int)的空间
  1. new运算符的内部实现:
      前面说过new是专门为C++创建动态对象而设计出来的,它的功能就是:动态分配内存+初始化对象(即调用了构造函数),那么分配内存时它是否像和malloc一样的实现机制?答案当然不是啦。
      new会调用相应的 operator new(size_t) 函数,再调用malloc动态分配内存。如果 operator new(size_t) 不能成功获得内存,则调用 new_handler() 函数用于处理new失败问题,抛出异常。分配好空间后,对在分配到的动态内存块上初始化相应类型的对象(即构造函数)并返回其首地址。如果调用构造函数初始化对象时抛出异常,则自动调用 operator delete(void*, void*) 函数释放已经分配到的内存。
      即new分了两部:调用operator new 分配空间+调用构造函数初始化对象。
      来图镇楼:
       这里写图片描述

  2. delete运算符的内部实现:
      delete的功能和new刚好相反,你new分配了空间并且调用了构造,那么我delete就是要和你对着来,我delete作用就是调用析构函数并且释放空间(注意顺序)。
      你new有对应的operator new实行机制,那么我delete也有对应的实行机制,即operator delete。
      所以delete分两步:调用析构函数清理+调用operator delete释放空间。
      来图镇楼:
      这里写图片描述

. new/delete的升华:

  1. new/delete和malloc/free最大的区别在于错误处理方式不同:
      malloc/free是面向过程的。面向过程的语言错误处理方式是返回值。
      而new/delete是面向对象的,出错则会抛异常。
    但是我们又发现new/delete的底层其实调用了malloc/free,那么它们怎么实现抛异常,其实就是它们中间的operator new和operatoe delete起了作用。operator new/delete具有封装性,将malloc/free实行了封装,使其符合面向对象语言的规范,达到了错误抛异常的目的。

  2. 对于对象数组:
      什么叫对象数组呢,直接上代码镇楼:

class AA {
public:
   AA()
   {
     _a = new int[10];
     cout << "AA()" << endl;
   }
   ~AA()
   {
      delete[] _a;
      cout << "~AA()" << endl;
   }
private:
   int* _a;
};
void Test()
{
    AA* p1=new AA[10];//创建了十个对象,即对象数组
    delete[] p1;
}

  那么问题来了,前面的new一个对象,delete清理一个对象,没有任何问题,那么对于现在而言,new了10个对象,那么delete怎么知道要清理多少个对象,即调用多少次析构函数?
  答案是:new开辟空间时,会多分配四个字节用来存放开辟对象的个数,比如上述例子要求开辟10个对象的大小,所以会在多分配的那四个字节中存放数字10,这样delete就可以调用10次析构函数。但是当类中没有用户定义的析构函数时,不会多分配那四个字节。即:多分配四个字节取决于有无用户定义的析构函数。
  所以new[n]做了这几件事:
   调用operator new分配空间 + 调用N次构造函数分别初始化每个对象
  同样delete[n]做了这几件事:
   调用N次析构函数清理对象 + 调用operator delete释放空间
  这里写图片描述
  所以又引出了另一个问题,new和delete的匹配使用?

. new、delete和new[]、delete[]的匹配使用:

  老规矩,上代码镇楼:

class AA {
public:
        AA()
        {
               _a = new int[10];
               cout << "AA()" << endl;
        }
        ~AA()
        {
               delete[] _a;
               cout << "~AA()" << endl;
        }
private:
        int* _a;
};
void Test()
{
    int* p1 = new int[10];
    free(p1);//new分配空间-->operator new-->malloc   free释放
    AA* p2 = (AA*)malloc(sizeof(AA));//没有调用构造函数
    delete p2;//分配空间malloc(没有调用构造函数),倘若构造函数有初始化,则这种方式
              //会将构造函数里的值=随机值,再次调用delete会将随机值释放,错误
    int* p3 = new int[10];//过程:new-->operator new-->malloc
    delete p3;//过程:delete-->operator delete-->free

    AA* p4 = new AA[10];//error(只调用了一次析构)
    delete p4;

    AA* p1 = new AA[10];//开空间,调用10次构造函数,多分配了四个字节
    delete[] p1;//调用10次析构函数+释放空间
}

最后感谢您的耐心,送图镇贴:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/CY071512/article/details/80020038