zoukankan      html  css  js  c++  java
  • new/new[]和delete/delete[]是如何分配空间以及释放空间的

        C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为或堆(heap)。程序可以用堆来存储动态分配的对象,即那些在程序运行时创建的对象。动态对象的生存期由程序来控制 ,当动态对象不再使用时,程序必须显式的销毁它们。new操作符就是从自由存储区上为对象动态分配内存空间的。这里的自由存储区可以是堆,或者静态区。

    1、new和delete的使用

        C++中通过一对运算符new和delete来完成动态内存分配。new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象初始化;delete接受一个动态对象的指针,销毁对象,并释放对应内存。使用示例如下:

     

     1 void Test()
     2 {
     3     int *pi1 = new int;           
     4 //pi1指向一个动态分配的4个字节(int型)、未初始化的无名对象;*pi1的值未定义
     5     int *pi2 = new int(2);        
     6 //pi2指向的对象的值是2,动态分配4个字节( 1个 int) 的空间并初始化为2
     7     int *pi3 = new int[3];        //动态分配12个字节( 3个 int) 的空间
     8     int *pi4 = new int();         //值初始化为0;*pi4为0
     9 
    10     delete pi1;
    11     delete pi2;
    12     delete [] pi3;
    13     delete pi4;
    14 }

         自由空间分配的内存是无名的,new无法为其分配的对象命名,而是返回一个指向该对象的指针。默认情况下,动态分配的对象是默认初始化的,即内置类型或组合类型的对象的值是未定义的,类类型的对象将用默认构造函数进行初始化。
        new和delete、 new[] 和delete[] 一定匹配使用 , 一定匹配使用 , 一定匹配使用 ! ! ! 重要的事说三遍! 否则可能出现内存泄露甚至崩溃的问题。
    2、深入探究new和delete、 new[] 和delete[]内部实现机制

    通过下面这段代码我们来详细比较一下new和delete、 new[] 和delete[]内部实现

     1 class Array
     2 {
     3     public :
     4     Array(size_t size = 10)//构造函数
     5         : _size(size)
     6         , _a(0)
     7     {
     8         cout << "Array(size_t size) " << endl;
     9         if (_size > 0)
    10         {
    11             _a = new int[size];
    12         }
    13     }
    14     ~Array()             //析构函数
    15     {
    16         cout << "~Array() " << endl;
    17         if (_a)
    18         {
    19             delete[] _a;
    20             _a = 0;
    21             _size = 0;
    22         }
    23     }
    24 private:
    25     int*_a;
    26     size_t _size;
    27 };
    28 void Test()
    29 {
    30     Array* p1 = (Array*)malloc(sizeof(Array));
    31     Array* p2 = new Array;
    32     Array* p3 = new Array(20);
    33     Array* p4 = new Array[10];
    34     free(p1);
    35     delete p2;
    36     delete p3;
    37     delete[] p4;
    38 }
    39 int main()
    40 {
    41     Test();
    42     getchar();
    43     return 0;
    44 }

    转到反汇编可以看到,在call指令处调用了operator new:

    转到定义处可以看到operator new 的具体原型:

    其实在operator new的底层同样调用了malloc分配空间,它先为对象分配所申请的内存空间,然后底层调用构造函数构造对象

    再按F10程序来到了构造函数

    执行完之后,输出

    此时new已经完成了申请空间的任务,且调用构造函数创建了对象。同样,detele的定义如下

     

    而delete是先调用析构函数清除对象。然后调用operator detele释放空间。

    按F10跳转到了析构函数,析构之后:

     

    然后空间才被释放:

    new []与delete []内部执行过程相同,只是底部调用的是operator new []和operator delete []

    Array* p4 = new Array[10];
    delete[] p4;

        执行这两条语句的时候实际上调用operator new[](10*sizeof(Array)+4)分配大小为10*sizeof(Array)+4空间,其中多的四个字节空间用于存放N(10)这个数字以便于delete中调用析构函数析构对象(调用析构函数的次数),空间申请好了之后调用构造函数创建对象。delete[] p4执行的时候首先取N(10)对象个数,然后调用析构函数析构对象,最后用operator delete[]函数释放空间。

    3、关键字之间的匹配使用问题

     1 void Test ()
     2 {
     3 // 以下代码没有匹配使用, 会发生什么? 有内 存泄露吗? 会崩 溃吗?
     4 int* p4 = new int;
     5 int* p5 = new int(3) ;
     6 int* p6 = new int[3] ;
     7 int* p7 = (int*) malloc(sizeof (int) ) ;
     8 delete[] p4 ;
     9 delete p5 ;
    10 free(p5 ) ;
    11 delete p6 ;
    12 delete p7 ;
    13 }

    运行结果:没有崩溃。但是当把int换成自定义类型之后则会出现问题。因为内置类型一般不会调用构造函数和析构函数,而自定义类型会,所以是析构对象的时候出现内存泄漏,导致程序崩溃。虽然弄清了问题,但还是建议任何类型都要匹配使用。

    4、定位new表达式

    new表达式,默认下把内存开辟到堆区。使用定位new表达式,可以在指定地址区域(栈区、堆区、静态区)构造对象,这好比是把内存开辟到指定区域。

    定位new表达式调用 void *operator new(size_t, void *); 分配内存。其常见形式有:

    1     new(address) type;
    2     new(address) type(initializers);
    3     new(address) type[size];
    4     new(address) type[size]{braced initializer list};

    address必须是个指针,指向已经分配好的内存。

    示例代码:

     1 #include <iostream>
     2 using namespace std;
     3 char addr1[100];  //把内存分配到全局/静态区
     4 int main()
     5 {
     6  char addr2[100];  //把内存分配到栈区
     7  char *addr3 = new char[100];  //把内存分配到堆区
     8  cout << "addr1 = " << (void*)addr1 << endl;
     9  cout << "addr2 = " << (void*)addr2 << endl;
    10  cout << "addr3 = " << (void*)addr3 << endl;
    11  int *p = nullptr;
    12  //把对象构造到静态区
    13  p = new(addr1)int;
    14  *p = 1;
    15  cout << (void*)p << "  " << *p << endl;
    16  //把对象构造到栈区
    17  p = new(addr2)int;
    18  *p = 2;
    19  cout << (void*)p << "  " << *p << endl;
    20  //把内存分配到堆区
    21  p = new(addr3)int;
    22  *p = 3;
    23  cout << (void*)p << "  " << *p << endl;
    24  cin.get();
    25  return 0;
    26 }

    程序中,首先使用变量或new为对象分配空间,然后通过定位new表达式,完成构造函数的调用,将对象创建在已经被分配好的内存中。

    定位new表达式不能调用delete删除 placement new的对象,需要人为的调用对象的析构函数,并且人为的释放掉占用的内存。

     1     #include <iostream>   
     2     #include <new>   
     3       
     4     using namespace std;   
     5       
     6     const int chunk = 16;   
     7       
     8     class Foo   
     9     {   
    10     public:   
    11         int val(){return _val;}   
    12         Foo(){_val=0;}   
    13     private:   
    14         int _val;   
    15     };   
    16       
    17     int main()   
    18     {   
    19         // 预分配内存buf   
    20         char *buf = new char[sizeof(Foo) * chunk];   
    21       
    22         // 在buf中创建一个Foo对象  
    23         Foo *pb=new (buf) Foo;   
    24         // 检查一个对象是否被放在buf中  
    25         if(pb->val()==0) cout<<"new expression worked!"<<endl;   
    26         // 这里不存在与定位new表达式匹配的delete表达式,即:delete pb, 其实只是为了  
    27         // 释放内存的话,我们不需要这样的表达式,因为定位new表达式并不分配内存。  
    28         // 如果在析构函数中要做一些其他的操作呢?就要显示的调用析构函数。  
    29         // 当程序不再需要buf时,buf指向的内存被删除,它所包含的任何对象的生命期也就  
    30         // 都结束了。   
    31       
    32         delete[] buf;   
    33         return 0;   
    34     }  

    一句话:定位new表达式用于在已分配的原始空间中调用构造函数初始化一个对象。

    5、模拟实现new和delete

     1 class Test
     2 {};
     3 
     4 Test* newdelete( )
     5 {
     6     Test* p1 = NULL;
     7     //1、分配空间     2.利用new的定位表达式显式调用构造函数 
     8     if (p1 = (Test*)malloc(sizeof(Test)))
     9         return p1;
    10     else
    11         throw bad_alloc();  //内存分配失败时抛异常
    12     new(p1)Test;  //NEW(P1+I)Test(参数列表);
    13 
    14     //3、析构函数     4、释放空间  
    15     p1->~Test();
    16     free(p1);
    17 }
    18 
    19 Test* newdalete_(size_t N)
    20 {
    21     Test* p2 = NULL;
    22     //1、分配空间     2.显示调用构造函数
    23     if (p2 = (Test*)malloc(sizeof(Test)*N + 4))
    24         return p2;
    25     else
    26         throw bad_alloc();  //内存分配失败时抛异常
    27     *((int*)p2) = N;
    28     p2 = (Test*)((int*)p2 + 1); 
    29     for (int i = 0; i < N; ++i)
    30     {
    31         new(p2 + i)Test;
    32     }
    33 
    34     int n = *((int*)p2 - 1);
    35     //3、析构函数     4、释放空间 
    36     for (int i = 0; i < n; ++i)
    37     {
    38         p2[i].~Test();
    39         //(p1 + 1)->~AA();   //也可以  
    40     }
    41     free((int*)p2 - 1);
    42 }

    总结:
    1. operator new/operator delete operator new[] /operator delete[] 和 malloc/free用法一 样。
    2. 他们只负责分配空间/释放空间, 不会调用对象构造函数/析构函数来初始化/清理对象。
    3. 实际operator new和operator delete只是malloc和free的一层封装。

    【 new作用】 调用operator new分配空间。 调用构造函数初始化对象。

    【 delete作用】 调用析构函数清理对象 调用operator delete释放空间

    【 new[] 作用】 调用operator new分配空间。 调用N次构造函数分别初始化每个对象。

    【 delete[] 作用】 调用N次析构函数清理对象。  调用operator delete释放空间。

     

  • 相关阅读:
    Appium之启动第一个App
    Appium简介
    C语言-malloc,calloc,realloc 函数的使用(堆空间的使用)
    C语言-const 修饰符,static 和 extern修饰符
    C语言-字符串与指针,fgets 函数,fputs 函数
    C语言- 指针(初始化,作用,指针与整数的加减法,指针与指针的减法,指针与指针之间的比较,指针与数组,函数的关系,中括号的本质)
    C语言-字符串
    C语言-数组
    C语言-类型说明符 long,short,unsigned,signed
    C语言-char 类型基本概念
  • 原文地址:https://www.cnblogs.com/33debug/p/6622807.html
Copyright © 2011-2022 走看看