精华内容
下载资源
问答
  • 使用new和delete创建二维数组,作业习题中的一道,希望能有所帮助
  • C++的动态内存管理是通过new和delete两个操作来完成的,即用new来申请空间,用delete来释放空间。在使用new和delete时,注意以下原则。 1.newdelete需一一对应 用new操作申请空间,如果申请成功,必须在以后的某个...
  • C++new和delete详细解析

    2020-12-22 20:22:53
    C++中的new 和delete 是操作符,new 分配对象时候会自动调用构造函数,delete对象时候,会自动调用析构函数,而C语言中 malloc() free() 是函数。 理论上malloc free newdelete 可以混搭用,但是好不要...
  • C++的new和delete详解

    万次阅读 多人点赞 2019-06-21 15:03:54
    new和delete的内部实现 C++中如果要在堆内存中创建销毁对象需要借助关键字new和delete来完成。比如下面的代码 class CA { public: CA():m_a(0){} CA(int a):m_a(a){} virtual void foo(){ cout<<m_a...

    new和delete的内部实现

    C++中如果要在堆内存中创建和销毁对象需要借助关键字new和delete来完成。比如下面的代码

    class CA
    {
        public:
           CA():m_a(0){}
           CA(int a):m_a(a){}
    
           virtual void foo(){ cout<<m_a<<endl;}
           int m_a;
    };
    
    void main()
    {
           CA *p1 = new CA;
           CA *p2 = new CA(10);
           CA *p3 = new CA[20];
    
           delete p1;
           delete p2;
           delete[] p3;
    }
    
    

    new和delete既是C++中的关键字也是一种特殊的运算符。

       void* operator new(size_t size);
       void* operator new[](size_t size);
       void  operator delete(void *p);
       void  operator delete[](void *p);
    

    new和delete不仅承载着内存分配的功能还承载着对象构造函数的调用功能,因此上面的对象创建代码其实在编译时会转化为如下的实现:

          CA *p1 = operator new(sizeof(CA));  //分配堆内存
          CA::CA(p1);   //调用构造函数
    
          CA *p2 = operator new(sizeof(CA));  //分配堆内存
          CA::CA(p2, 10);   //调用构造函数
         
          CA *p3 = operator new[](20 * sizeof(CA));
          CA *pt = p3;
          for (int i = 0; i < 20; i++)
         {
             CA::CA(pt);
             pt += 1;
         }
    
         CA::~CA(p1);
         operator delete(p1);
         
         CA::~CA(p2);
         operator delete(p2);
    
         CA *pt = p3;
         for (int i = 0; i < 20; i++)
         {
              CA::~CA(pt);
              pt += 1;
         }
         operator delete[](p3);
    
    

    看到上面的代码也许你会感到疑惑,怎么在编译时怎么会在源代码的基础上插入这么多的代码。这也是很多C程序员吐槽C++语言的原因:C++编译器会偷偷插入很多未知的代码或者对源代码进行修改和处理,而这些插入和修改动作对于程序员来说是完全不可知的!
    言归正传,我们还能从上面的代码中看出new和delete操作其实是分别进行了2步操作:1.内存的分配,2.构造函数的调用;3.析构函数的调用,4.内存的销毁。所以当对象是从堆内存分配时,构造函数执前内存就已经完成分配,同样当析构函数执行完成后内存才会被销毁。
    这里面一个有意思的问题就是当我们分配或者销毁的是数组对象时,系统又是如何知道应该调用多少次构造函数以及调用多少次析构函数的呢?答案就是在内存分配里面。当我们调用operator new[]来分配数组对象时,编译器时系统内部会增加4或者8字节的分配空间用来保存所分配的数组对象的数量。当对数组对象调用构造和析构函数时就可以根据这个数量值来进行循环处理了。因此上面对数组对象的分配和销毁的真实代码其实是按如下方式处理的:

         //  CA *p3 = new CA[20]; 这句代码在编译时其实会转化为如下的代码片段
         unsigned long *p = operator new[](20 * sizeof(CA) + sizeof(unsigned long));  //64位系统多分配8字节
         *p = 20;   //这里保存分配的对象的数量。
         CA *p3 = (CA*)(p + 1);
         CA *pt = p3;
         for (int i = 0; i < *p; i++)
         {
             CA::CA(pt);
             pt += 1;
         }
    
    
        // delete[] p3;   这句代码在编译时其实会转化为如下的代码片段
         unsigned long *p =  ((unsigned long*)p3)  - 1;
         CA *pt = p3;
         for (int i = 0; i < *p; i++)
         {
              CA::~CA(pt);
              pt += 1;
          }
          operator delete[](p);
    

    可见C++中为我们隐藏了多少细节啊! 这里需要注意的是分配数组内存时会增加额外的存储空间来保存数量的情况只会发生在对类进行内存分配的情况,而对于基础类型进行内存分配则不会增加额外的空间来保存数量,比如下面的代码:

        int *p = new int[30];
    

    之所以会有这种差异的原因是因为类对象的构建和销毁时存在着构造函数和析构函数的调用,因此必须要保存数量来对每个元素进行函数调用的遍历处理,而普通类型则没有这个步骤。这也是编译器对各种类型数据的构建和销毁的一个优化处理。

    既然new和delete操作默认是从堆中进行内存分配,而且new和delete又是一个普通的运算符函数,那么他内部是如何实现呢?其实也很简单。我们知道C语言中堆内存分配和销毁的函数是malloc/free。因此C++中对系统默认的new和delete运算符函数就可以按如下的方法实现:

    void * operator new(size_t size)
    {
         return malloc(size);
    } 
    
    void * operator new[](size_t size)
    {
         return malloc(size);
    }
    
    void operator delete(void *p)
    {
         free(p);
    }
    
    void operator delete[](void *p)
    {
        free(p);
    }
    

    这里需要注意的是你在代码里面使用new关键字和使用operator new操作符所产生的效果是不一样的。如果你在代码里面使用的是new关键字那么系统内部除了会调用operator new操作符来分配内存还会调用构造函数,而如果你直接使用operator new时则只会进行内存分配而不会执行任何构造就比如下面的代码:

       CA *p1 = new CA;   //这里会分配内存和执行构造函数
    
       CA *p2 = operator new(sizeof(CA));   //这里只是执行了普通的堆内存分配而不会调用构造函数
    

    上述的伪代码都是在运行时通过查看汇编语言而得出的结论,我是在XCODE编译器上查看运行的结果,有可能不同的编译器会有一些实现的差异,但是不管如何要想真实的了解内部实现原理还是要懂一些汇编的知识为最好。

    placement技术

    系统默认的new关键字除了分配堆内存外还进行构造函数的调用。而实际中我们可能有一些已经预先分配好的内存区域,我们想在这些已经分配好的内存中来构建一个对象。还有一种情况是不希望进行频繁的堆内存分配和释放而只是对同一块内存进行重复的对象构建和销毁。就如下面的代码:

    
    char buf1[100];
    CA *p1 = (CA*)buf1;
    CA::CA(p1);
    p1->foo();
    p1->m_a = 10;
    
    
    char *buf2 = new char[sizeof(CA)];
    CA *p2 = (CA*)buf2;
    CA::CA(p2);
    p2->foo();
    p2->m_a = 20;
    
    
    p1->~CA();
    p2->~CA();
    
    delete[] buf2;
    
    

    可以看出代码中buf1是栈内存而buf2是堆内存,这两块内存区域都是已经分配好了的内存,现在我们想把这些内存来当做CA类的对象来使用,因此我们需要对内存调用类的构造函数CA::CA()才可以,构造函数的内部实现会为内存区域填充虚表指针,这样对象才可以调用诸如foo虚函数。但是这样写代码不够优雅,那么有没有比较优雅的方法来实现在一块已经存在的内存上来构建对象呢? 答案就是 placement技术。 C++中的仍然是使用new和delete来实现这种技术。new和delete除了实现默认的操作符外还重载实现了如下的操作符函数:

    void* operator new(size_t  size, void *p)
    {
       return p;
    }
    
    void* operator new[](size_t size, void *p)
    {
       return p;
    }
    
    void operator delete(void *p1, void *p2)
    {
       // do nothing..
    }
    
    void operator delete[](void *p1, void *p2)
    {
       // do nothing..
    }
    
    

    我们称这四个运算符为 placement new 和 placement delete 。通过这几个运算符我们就可以优雅的实现上述的功能:

    char buf1[100];
    CA *p1 = new(buf1) CA(10)   //调用 operator new(size_t, void*)
    p1->foo();
    
    
    char *buf2 = new char[sizeof(CA)];
    CA *p2 = new(buf2) CA(20);     //调用 operator new(size_t, void*)
    p2->foo();
    
    
    p1->~CA();
    operator delete(p1, buf1);  //调用 operator delete(void*, void*)
    
    p2->~CA();
    operator delete(p2, buf2);  //调用 operator delete(void*, void*)
    
    delete[] buf2;
    
    

    上面的例子里面发现通过placement new可以很优雅的在现有的内存中构建对象,而析构时不能直接调用delete p1, delete p2来销毁对象,必须人为的调用析构函数以及placement delete 函数。并且从上面的placement delete的实现来看里面并没有任何代码,既然如此为什么还要定义一个placement delete呢? 答案就是C++中的规定对new和delete的运算符重载必须是要成对实现的。而且前面曾经说过对delete的使用如果带了operator前缀时就只是一个普通的函数调用。因此为了完成析构以及和new操作符的匹配,就必须要人为的调用对象的析构函数以及placement delete函数。
    除了上面举的例子外placement技术的使用还可以减少内存的频繁分配以及提升系统的性能。

    void main()
    {
          for (int i = 0; i < 10000; i++)
          {
               CA *p = new CA(i);
               p->foo();
               delete p;
          }
    }
    
    

    例子里面循环10000次,每次循环都创建一个堆内存对象,然后调用虚函数foo后再进行销毁。最终的结果是程序运行时会进行10000次的频繁的堆内存分配和销毁。很明显这是有可能会影响系统性能的而且还有可能发生堆内存分配失败的情况。而如果我们借助placement 技术就可以很简单的解决这些问题。

    
    void main()
    {
          char *buf = new[](sizeof(CA));
          for (int i = 0; i < 10000; i++)
          {
                CA *p = new(buf) CA(i);
                p->foo();
                p->~CA();
                operator delete(p, buf);
          }
          delete[] buf;
    }
    

    上面的例子里面只进行了一次堆内存分配,在循环里面都是借助已经存在的内存来构建对象,不会再分配内存了。这样对内存的重复利用就使得程序的性能得到非常大的提升。

    new和delete运算符重载

    发现一个很有意思的事情就是越高级的语言就越会将一些系统底层的东西进行封装并形成一个语言级别的关键字来使用。比如C++中的new和delete是用于构建和释放堆内存对象的关键字,又比如go语言中chan关键字是用于进行同步或者异步的队列数据传输通道。
    C++语言内置默认实现了一套全局new和delete的运算符函数以及placement new/delete运算符函数。不管是类还是内置类型都可以通过new/delete来进行堆内存对象的分配和释放的。对于一个类来说,当我们使用new来进行构建对象时,首先会检查这个类是否重载了new运算符,如果这个类重载了new运算符那么就会调用类提供的new运算符来进行内存分配,而如果没有提供new运算符时就使用系统提供的全局new运算符来进行内存分配。内置类型则总是使用系统提供的全局new运算符来进行内存的分配。对象的内存销毁流程也是和分配一致的。
    new和delete运算符既支持全局的重载又支持类级别的函数重载。下面是这种运算符的定义的格式:

    //全局运算符定义格式
    void * operator new(size_t size [, param1, param2,....]);
    void operator delete(void *p [, param1, param2, ...]);
    
    //类内运算符定义格式
    class CA
    {
      void * operator new(size_t size [, param1, param2,....]);
      void operator delete(void *p [, param1, param2, ...]);
    };
    
    

    对于new/delete运算符重载我们总有如何下规则:

    • new和delete运算符重载必须成对出现
    • new运算符的第一个参数必须是size_t类型的,也就是指定分配内存的size尺寸;delete运算符的第一个参数必须是要销毁释放的内存对象。其他参数可以任意定义。
    • 系统默认实现了new/delete、new[]/delete[]、 placement new / delete 6个运算符函数。它们都有特定的意义。
    • 你可以重写默认实现的全局运算符,比如你想对内存的分配策略进行自定义管理或者你想监测堆内存的分配情况或者你想做堆内存的内存泄露监控等。但是你重写的全局运算符一定要满足默认的规则定义。
    • 如果你想对某个类的堆内存分配的对象做特殊处理,那么你可以重载这个类的new/delete运算符。当重载这两个运算符时虽然没有带static属性,但是不管如何对类的new/delete运算符的重载总是被认为是静态成员函数。
    • 当delete运算符的参数>=2个时,就需要自己负责对象析构函数的调用,并且以运算符函数的形式来调用delete运算符。

    一般情况下你不需要对new/delete运算符进行重载,除非你的整个应用或者某个类有特殊的需求时才会如此。下面的例子你可以看到我的各种运算符的重载方法以及使用方法:

    //CA.h
    
    class CA
    {
    public:
        //类成员函数
        void * operator new(size_t size);
        void * operator new[](size_t size);
        void * operator new(size_t size, void *p);
        void * operator new(size_t size, int a, int b);
        
        void operator delete(void *p);
        void operator delete[](void *p);
        void operator delete(void *p, void *p1);
        void operator delete(void *p, int a, int b);
    };
    
    class CB
    {
    public:
        CB(){}
    };
    
    
    //全局运算符函数,请谨慎重写覆盖全局运算符函数。
    void * operator new(size_t size);
    void * operator new[](size_t size);
    void * operator new(size_t size, void *p) noexcept;
    void * operator new(size_t size, int a, int b);
    
    void operator delete(void *p);
    void operator delete[](void *p);
    void operator delete(void *p, void *p1);
    void operator delete(void *p, int a, int b);
    
    .......................................................
    //CA.cpp
    
    
    void * CA::operator new(size_t size)
    {
        return malloc(size);
    }
    
    void * CA::operator new[](size_t size)
    {
        return malloc(size);
    }
    
    void * CA::operator new(size_t size, void *p)
    {
        return p;
    }
    
    void* CA::operator new(size_t size, int a, int b)
    {
        return malloc(size);
    }
    
    void CA::operator delete(void *p)
    {
        free(p);
    }
    
    void CA::operator delete[](void *p)
    {
        free(p);
    }
    
    void CA::operator delete(void *p, void *p1)
    {
        
    }
    
    void CA::operator delete(void *p, int a, int b)
    {
        free(p);
    }
    
    
    void * operator new(size_t size)
    {
        return  malloc(size);
    }
    
    void * operator new[](size_t size)
    {
        return malloc(size);
    }
    
    void * operator new(size_t size, void *p) noexcept
    {
        return p;
    }
    
    void* operator new(size_t size, int a, int b)
    {
        return malloc(size);
    }
    
    void operator delete(void *p)
    {
        free(p);
    }
    
    void operator delete[](void *p)
    {
        free(p);
    }
    
    void operator delete(void *p, void *p1)
    {
        
    }
    
    void operator delete(void *p, int a, int b)
    {
        free(p);
    }
    
    ..................................
    //main.cpp
    
    int main(int argc, const char * argv[]) {
        
        char buf[100];
    
        CA *a1 = new CA();   //调用void * CA::operator new(size_t size)
        
        CA *a2 = new CA[10];  //调用void * CA::operator new[](size_t size)
        
        CA *a3 = new(buf)CA();  //调用void * CA::operator new(size_t size, void *p)
        
        CA *a4 = new(10, 20)CA();  //调用void* CA::operator new(size_t size, int a, int b)
        
        
        delete a1;  //调用void CA::operator delete(void *p)
        
        delete[] a2;  //调用void CA::operator delete[](void *p)
        
        //a3用的是placement new的方式分配,因此需要自己调用对象的析构函数。
        a3->~CA();
        CA::operator delete(a3, buf);  //调用void CA::operator delete(void *p, void *p1),记得要带上类命名空间。
    
        //a4的运算符参数大于等于2个所以需要自己调用对象的析构函数。
        a4->~CA();
        CA::operator delete(a4, 10, 20); //调用void CA::operator delete(void *p, int a, int b)
        
        //CB类没有重载运算符,因此使用的是全局重载的运算符。
        
        CB *b1 = new CB();  //调用void * operator new(size_t size)
     
        
        CB *b2 = new CB[10]; //调用void * operator new[](size_t size)
        
        //这里你可以看到同一块内存可以用来构建CA类的对象也可以用来构建CB类的对象
        CB *b3 = new(buf)CB();  //调用void * operator new(size_t size, void *p)
        
        CB *b4 = new(10, 20)CB(); //调用void* operator new(size_t size, int a, int b)
        
    
        delete b1;  //调用void operator delete(void *p)
    
        
        delete[] b2;   //调用void operator delete[](void *p)
        
        
        //b3用的是placement new的方式分配,因此需要自己调用对象的析构函数。
        b3->~CB();
        ::operator delete(b3, buf);  //调用void operator delete(void *p, void *p1)
        
        //b4的运算符参数大于等于2个所以需要自己调用对象的析构函数。
        b4->~CB();
        ::operator delete(b4, 10, 20);  //调用void operator delete(void *p, int a, int b)
       
       return 0;
    } 
    

    我是在XCODE上测试上面的代码的,因为重写了全局的new/delete运算符,并且内部是通过malloc来实现堆内存分配的, malloc函数申明了不能返回NULL的返回结果检测:
    void *malloc(size_t __size) __result_use_check __alloc_size(1);
    因此有可能你在测试时会发生崩溃的问题。如果出现这个问题你可以尝试着注释掉对全局new/delete重写的代码,再运行查看结果。 可见如果你尝试着覆盖重写全局的new/delete时是有可能产生风险的。

    对象的自动删除技术

    一般来说系统对new/delete的默认实现就能满足我们的需求,我们不需要再去重载这两个运算符。那为什么C++还提供对这两个运算符的重载支持呢?答案还是在运算符本身具有的缺陷所致。我们知道用new关键字来创建堆内存对象是分为了2步:1.是堆内存分配,2.是对象构造函数的调用。而这两步中的任何一步都有可能会产生异常。如果说是在第一步出现了问题导致内存分配失败则不会调用构造函数,这是没有问题的。如果说是在第二步构造函数执行过程中出现了异常而导致无法正常构造完成,那么就应该要将第一步中所分配的堆内存进行销毁。C++中规定如果一个对象无法完全构造那么这个对象将是一个无效对象,也不会调用析构函数。为了保证对象的完整性,当通过new分配的堆内存对象在构造函数执行过程中出现异常时就会停止构造函数的执行并且自动调用对应的delete运算符来对已经分配的堆内存执行销毁处理,这就是所谓的对象的自动删除技术。正是因为有了对象的自动删除技术才能解决对象构造不完整时会造成内存泄露的问题。

    当对象构造过程中抛出异常时,C++的异常处理机制会在特定的地方插入代码来实现对对象的delete运算符的调用,如果想要具体了解情况请参考C++对异常处理实现的相关知识点。

    全局delete运算符函数所支持的对象的自动删除技术虽然能解决对象本身的内存泄露问题,但是却不能解决对象构造函数内部的数据成员的内存分配泄露问题,我们来看下面的代码:

    class CA
    {
      public:
        CA()
        {
              m_pa  = new int;
              throw 1;
        }
    
      ~CA()
       {
             delete m_pa;
             m_pa = NULL;
       }
    
     private:
          int *m_pa;
    };
    
    void main()
    {
         try
         {
               CA *p = new CA();
               delete p;  //这句代码永远不会执行
         }
         catch(int)
        {
              cout << "oops!" << endl;
        }
    }
    

    上面的代码中可以看到类CA中的对象在构造函数内部抛出了异常,虽然系统会对p对象执行自动删除技术来销毁分配好的内存,但是对于其内部的数据成员m_pa来说,因为构造不完整就不会调用析构函数来销毁分配的堆内存,这样就导致了m_pa这块内存出现了泄露。怎么解决这类问题呢? 答案你是否想到了? 那就是重载CA类的new/delete运算符。我们来看通过对CA重载运算符解决问题的代码:

    class CA
    {
    public:
        CA(){
            m_pa = new int;
            throw 1;
        }
        //因为对象构造未完成所以析构函数永远不会被调用
        ~CA()
        {
            delete m_pa;
            m_pa = NULL;
        }
        
        void * operator new(size_t size)
        {
            return malloc(size);
        }
        //重载delete运算符,把已经分配的内存销毁掉。
        void operator delete(void *p)
        {
            CA *pb = (CA*)p;
            if (pb->m_pa != NULL)
                delete pb->m_pa;
            
            free(p);
        }
        
    private:
        int *m_pa;
    };
    

    因为C++对自动删除技术的支持,当CA对象在构造过程中发生异常时,我们就可以通过重载delete运算符来解决那些在构造函数中分配的数据成员内存但又不会调用析构函数来销毁的数据成员的内存问题。这我想就是为什么C++中要支持对new/delete运算符在类中重载的原因吧。


     

    展开全文
  • new和delete的内部实现 C++中如果要在堆内存中创建销毁对象需要借助关键字new和delete来完成。比如下面的代码 class CA { public: CA()m_a(0){} CA(int a):m_a(a){} virtual void foo(){ cout<<m_a<...
  • C++中new和delete的介绍

    2020-12-25 22:47:54
    1.malloc,free和new,delete区别。 a.malloc,free是C/C++的标准库函数。new,delete是c++的操作符。 b.malloc申请的是内存,严格意义不是“对象”,new申请的可以理解为“对象”,new 时会调用构造函数,返回指向该...
  • 关于new和delete的详细用法。详细用法哟!new是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘,
  • C++ new和delete动态分配释放内存

    千次阅读 2020-09-22 21:29:50
    关于C++的关键字兼运算符: new和delete new new其实就是告诉计算机开辟一段新的空间,但是一般的声明不同的是, new开辟的空间在堆上,而一般声明的变量存放在栈上 。通常来说,当在局部函数中new出一段新的空间...

    关于C++的关键字兼运算符: new 和 delete

    new

    new关键字其实就是告诉计算机开辟一段新的空间,但是和一般的声明不同的是,new关键字开辟的空间在堆内存上,而一般声明的变量存放在栈内存上.通常来说,当在局部函数中new出一段新的空间,该段空间在局部函数调用结束后仍然能够使用,可以用来向主函数传递参数,另外需要注意的是,new的使用格式,new出来的是一段空间的首地址.所以一般需要用指针来存放这段地址


    为什么需要new来开辟内存空间:

    比如说数组的长度是预先定义好的,在整个程序中固定不变,但C++ 不允许定义元素个数不确定的数组,例如:

    int n;//未进行定义的变量n
    int a[n];  //这种定义是不允许的
    

    但是在实际的编程中,往往会出现所需的内存空间大小取决于实际要处理的数据多少,而实际要处理的数据数量在编程时无法确定的情况。如果总是定义一个尽可能大的数组,又会造成空间浪费.何况,这个“尽可能大”到底应该多大才够呢

    为了解决上述问题,C++提供了一种动态内存分配机制 ,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配一片内存空间用于存放数据.此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为“动态内存分配”

    总结:动态内存分配是一种,内存分配是在程序运行中进行的,而不是在编译时就确定的内存分配方式


    C++ 中,通过new运算符来实现动态内存分配,new运算符的第一种用法如下:

    T *p = new M;
    

    其中M任意类型名,p类型为T*的指针

    这个语句会动态分配出一片大小为sizeof(M)字节大小的内存空间,且将这段内存空间的起始地址(首地址)赋值给int类型的指针p,例如:


    典型的new使用方式

    int *p;
    p=new int;
    *p=5;
    

    第一行 :建立类型为int的指针p
    第二行p=new int此行动态分配了一片4个字节大小(int)的内存空间,而指针p指向指向这片空间
    第三行:通过p读写这片空间


    在使用new动态分配内存空间的同时对指针值进行定义

    int* p = new int(123);//可以之间在new对象中进行初始化
    cout<< *p << endl;//输出结果为 “123”
    

    使用new动态分配内存一个任意大小的数组

    例如:

    T *p =new M[N];
    

    在以上代码中
    M任意类型名
    p是类型为T*的指针
    N代表示元素个数
    []内的值可以为任何值为正整数的表达式(表达式内可包含你变量和函数调用等值),以上语句动态分配出N x sizeof(M)个字节的内存空间这片空间的起始地址被赋值给p,例如:

    //todo使用new申请多个对象,例如申请1024个int对象如下:
    	int* p2 = new int[10];//用[]指定对象的个数
    	cout << "使用new申请多个对象的结果为:\n\n";
    	for (int i = 0; i < 10; i++)
    	{
    		p2[i] = i + 1;
    		cout << p2[i]<< endl;//打印指针数组时直接输入数组名即可 因为数组名就是数组指针的首地址
    	}
    
    	delete[] p2;//如果new的时候用了[],则释放的时候使用delete[]操作符
    

    结果:
    在这里插入图片描述

    注意: 如果new的时候用了[],则释放的时候使用delete操作符后面也要跟上一个[]符号,表示释放数组中的所有元素


    delete

    程序从操作系统动态分配所得的内存空间在使用完后应该释放,交还操作系统,以便操作系统将这片内存空间分配给其他程序使用.C++ 中提供了delete 运算符,用以释放动态分配的内存空间.delete运算符的基本用法如下:

    在动态分配一般变量的内存时:

    int p=new int;
    delete p;
    

    在动态分配数组变量的内存时:

    int p=new int[10];
    delete []p;
    

    其中,p是指向动态分配内存的指针,且p必须指向动态分配(new)的内存空间,否则运行时将报错,例如:

    int* p = new int;
    *p = 5;
    delete p;
    delete p;  //本句会导致程序出错
    
    int* p = NULL;//将p这个指针变量设置为NULL,防止野指针的出现和内存泄漏
    

    上面的第一条delete语句正确地释放了动态分配的4个字节内存空间,第二条delete语句会导致程序出错,因为p所指向的空间已经释放,此时指针变量p虽然不再是指向动态分配的内存空间的指针了,但此时p这个指针还存在,但此指针已不指向任何一段内存,为了 防止内存的泄漏和野指针的出现,我们还需将p这个指针变量设置为NULL


    注意:🎯

    1如果动态分配了一个数组,但是却用delete p的方式释放,没有用[],则编译时没有问题,运行时也一般不会发生错误,但实际上会导致动态分配的数组没有被完全释放

    2.牢记,用 new 运算符动态分配的内存空间,一定要用 delete 运算符释放.否则,即便程序运行结束,这部分内存空间仍然不会被操作系统收回,从而成为被白白浪费掉的内存垃圾.这种现象也称为内存泄露

    3.如果一个程序不停地进行动态内存分配而总是没有释放,那么可用内存就会被该程序大量消耗,即便该程序结束也不能恢复.这就会导致操作系统运行速度变慢,甚至无法再启动新的程序.但是,只要重新启动计算机,这种情况就会消失

    4.编程时如果进行了动态内存分配,那么一定要确保其后的每一条执行路径都能释放它,另外还要注意,释放一个指针,并不会使该指针的值变为 NULL(也就是说释放后指针的指向的变量的值不变)

    来自
    C++ new和delete(C++动态分配和释放内存)🔍
    源自C++ 虚方法项目

    展开全文
  • C++ new delete 的讲解

    千次阅读 2019-01-01 19:55:41
    运算符 new delete 看起来很简单,但是它们也有不少的知识点。对于new来说就有 new operator、operator new、placement new new operator[]等。对于 delete 也有 delete operator、poerator deletedelete ...

    概述

    运算符 new 和 delete 看起来很简单,但是它们也有不少的知识点。对于new来说就有 new operator、operator new、placement new 和 new operator[]等。对于 delete 也有 delete operator、poerator delete、delete operator[] 等。下面就进行按个整理。

    1、new operator

    平时申请动态内存的 new 操作就是new operator。它其实由两个步骤完成:

    1、分配足够的内存,用来放置某类型对象
    2、调用构造函数,为刚才分配内存中的那个对象设定初值

    new operator 调用某个函数,执行内存分配的动作,这个函数就是 operator new。

    2、operatr new

    上面说了一个分配内存的函数叫 operator new ,可以用来改变内存分配行为。其函数声明通常为:

    void* operator new( size_t size);
    

    此函数返回一个指针,指向一块原始的、未设初始值的内存。size_t是分配内存的大小。

    你可以对 operator new 重载,加上额外的参数,但第一个参数类型必须总是 size_t。

    和malloc一样,operator new 唯一的任务就是分配内存。它不知道什么时候进行初始化操作。

    3、placement new

    在已分配好的内存上只调用对象的构造函数进行对象构建,需要使用placement new。例如:

    class Widget
    {
    public:
    	Widget( int widgetSize )
    	......
    }
    
    Widget* constructWidgetInBuffer( void* buffer, int widgetSize )
    {
    	return new ( buffer ) Widget ( widgetSize );
    }
    

    此函数返回一个指针,指向 Widget 对象,它被构造于传递给次函数的内存缓冲区上。

    其实placement new 是 operator new 的重载用法,其中指定一个额外自变量(buffer)作为 new operator “隐式调用operator new”时所用。类似于:

    void* operator new ( size_t, void* location )
    {
    	return location;
    }
    

    size_t是哑元参数,为了防止编译器报错。

    4、operator delete

    为了避免资源泄漏,每一个动态分配行为都必须匹配一个相应的释放动作

    operator delete 就是 operator new 对应的版本,通常声明如下:

    void operator delete ( void* memoryToBeDeallocated );
    

    和free()一样,此函数只会进行内存的释放,不会做其它动作。

    5、delete operator

    delete operator 是 new operator 的对应版本,它首先调用对象的析构函数,然后调用operator delete函数进行内存的释放。如果你这样写代码:

    Widget* ptr = new Widget(10);
    ......
    delete ptr;
    

    则编译器会给扩展成类似的代码:

    ptr->~Widget();
    operator delete(ptr);
    

    释放内存的函数要和申请内存的函数匹配使用

    6、数组的 new

    上面介绍的函数都只在一个对象上操作。对于数组而言,使用的仍然是new operator。例如:

     Widget* ptr = new Widget[ 10 ];
    

    虽然使用相同的 new operator , 但是行为与之前的产生单个对象的情况略微不同。主要有两点:

    1、一个名为operator new[] 的函数负责分配内存。也可以被重载。
    2、调用构造函数的次数不一样。数组版的 new operator 必须针对数组中的每个对象调用一个构造函数。

    它是如何记录元素的个数?在申请内存时,会额外配置一个内存放置元素的个数

    7、数组的 delete

    数组的delete要使用[ ],它会为每个数组中的元素调用析构函数。如:

     Widget* ptr = new Widget[ 10 ];
     ......         
     delete[] ptr;
    

    和new operator一样,也分为两个步骤:
    1、对数组中的每个元素调用析构函数。
    2、调用 operator delete[] 释放内存。

    只有的[] 出现时,编译器才寻找数组的维度,否则它便假设只有单独一个object要被删除。如:

    delete ptr;
    

    则只能调用数组第一个元素的析构函数,其它元素不会调用析构函数。然后调用 operator delete[] 释放内存。

    8、总结

    通过上面可以看出 new operator 就是先进行了 operator new ,然后在调用placement new。数组就相当于重复调用。

    new operator 和 delete operator 是语言内建的,就像sizeof一样,不能改变其意义。但是它们所调用的内存分配/释放函数则不然。想要定制new operator 和 delete operator 的行为,我后面会进行整理。

    9、new与malloc的区别

    9.1、申请的内存所在位置

    new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

    那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

    9.2、返回类型安全性

    new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
    类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。关于C++的类型安全性可说的又有很多了。

    9.3、内存分配失败时的返回值

    new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
    判断两者是否失败的语句是:

    char* buffer = (char*)malloc(1024);
    if(buffer) {
        printf("malloc success!\r\n");    
    }
    free(buffer);
    
    // new的写法
    try {
        char* buffer = new char[1024];
    }
    catch(...) {
        printf("operator new error!\r\n");
    }
    delete []buffer;
    

    9.4、是否指定内存大小

    使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸

    感谢大家,我是假装很努力的YoungYangD(小羊)。

    参考资料:
    《More Effective C++》
    《深度探索 C++ 对象模型》
    https://www.cnblogs.com/shilinnpu/p/8945637.html

    展开全文
  • new和delete的底层实现原理

    千次阅读 2019-05-10 22:57:42
    - new/new[]和delete/delete[]是什么? new/new[]和delete/delete[]是操作符;是C++用来实现动态内存管理的操作符; new/new[] 操作符是用来申请空间的; delete/delete[]操作数是用来释放动态申请出来的空间; - ...

    - new/new[]和delete/delete[]是什么?

    new/new[]和delete/delete[]是操作符是C++用来实现动态内存管理的操作符;

    • new/new[] 操作符是用来申请空间的;
    • delete/delete[]操作数是用来释放动态申请出来的空间;

    - new/delete的实现原理

    new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,
    new在底层调用operator new全局函数来申请空间;
    delete在底层通过operator delete全局函数来释放空间;

    operator new ()全局函数原型:

    /*
    operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试
    执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
    */
    void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
    {
     // try to allocate size bytes
     void *p;
     while ((p = malloc(size)) == 0)
     if (_callnewh(size) == 0)
     {
     // report no memory
     // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
     static const std::bad_alloc nomem;
     _RAISE(nomem);
     }
     return (p);
    }
    

    operator delete ()全局函数原型:

    /*
    operator delete: 该函数最终是通过free来释放空间的
    */
    void operator delete(void *pUserData)
    {
    	_CrtMemBlockHeader * pHead;
    	
     	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     	
     	if (pUserData == NULL)
     		return;
    
     	_mlock(_HEAP_LOCK); /* block other threads */
     	__TRY
     		/* get a pointer to memory block header */
     		pHead = pHdr(pUserData);
     		
     		/* verify block type */
     		_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
     		
    		 _free_dbg( pUserData, pHead->nBlockUse );
    		
    	__FINALLY
    		_munlock(_HEAP_LOCK); /* release other threads */
    	__END_TRY_FINALLY
    	
    	 return;
    }
    /*
    free的实现
    */
    #define free(p) _free_dbg(p, _NORMAL_BLOCK)
    

    通过这两个全局函数的实现,知道了operator new 实际是通过malloc来申请空间的,operator delete实际是通过free来释放空间的;

    那么对于不同的类型,new和delete的处理方式是不同的:

    • 内置类型:
      如果申请的是内置类型的空间,new和malloc,delete和free基本类似;
      不同之处:
      new在申请空间失败时会抛异常;
      malloc在申请空间失败时会返回NULL;

    • 自定义类型:

      • new的原理:
        1. 调用operator new函数申请空间;
        2. 在申请的空间上执行构造函数,完成对象的构造;
      • delete的原理:
        1. 在空间上执行析构函数,完成对象中资源的清理工作;
        2. 调用operator delete函数释放对象的空间;
      • new[N]的原理:
        1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请;
        2. 在申请的空间上执行N次构造函数;
      • delete[N]的原理:
        1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理;
        2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间;

    读到这里,可能会有读者想问在new[T]和delete[T]过程中如何确定本次调用的是第几次函数?

    在使用new[ ]时,调用operator new[](size_t size) 函数,传参时传入的不是 sizeof(类型)*对象个数;而是在对象数组的大小上加上一个额外数据,用于编译器区分对象数组指针和对象指针以及对象数组大小。在VS2008下这个额外数据占4个字节,一个int大小。
    但是,这个额外数据对外是不可见的。

    keep Running

    展开全文
  •  比起C语言,c++中动态创建内存要简单多了,C++中使用new 和delete运算符来动态分配内存,当然,在c++同样可以使用mallocfree来申请释放内存,但是对于一个类来说,使用mallocfree时不会执行类的构造函数...
  • C++中重载new和delete的使用

    千次阅读 2018-01-06 21:46:12
    C++中重载new和delete的使用
  • new delete 的用法完美解析

    万次阅读 多人点赞 2018-02-09 11:27:14
    c++中new和delete的使用方法http://www.jb51.net/article/49537.htm对于计算机程序设计而言,变量对象在内存中的分配都是编译器在编译程序时安排好的,这... new和delete运算符是用于动态分配撤销内存的运算符...
  • new和delete底层解析

    千次阅读 2018-04-05 10:26:44
    1.new/delete 和operator new/operator delete和malloc/free的关系 2.new时底层处理的机制 3.new/delete和malloc/free的区别 1.new/delete 和operator new/operator delete和malloc/free的关系 先写个栗子看...
  • 主要介绍了C++基础入门教程(五):new和delete,本文讲解了动态分配内存、new和delete的配对、newdelete与reatin、release的关系、动态数组等内容,需要的朋友可以参考下
  • 1、new和delete是C++的运算符。 2、为什么会产生new和delete运算符? 对于计算机程序设计而言,变量对象在内存中的分配都是编译器在编译程序时安排好的,这带来了极大的不便,如数组必须大开小用,指针必须指向一...
  • C++ 类(new和delete运算符)

    万次阅读 2018-07-21 14:32:25
    new和delete运算符 new和delete运算符 a. new/delete 是C++的运算符;类似于malloc/free,程序运行(动态)得开辟内存空间(堆); b. new 可以为内置类型的变量开辟空间,数组变量,类的对象开辟空间。这是在堆上开辟...
  • 第一部分: new和delete的实现原理 开始谈之前我们应该了解另一个概念“operator new“operator delete”: new操作符调用一个函数来完毕必需的内存分配,你可以重写或重载这个函数来改变它的行为。new...
  • C++动态内存:(二)重载new和delete

    万次阅读 多人点赞 2017-03-22 10:40:13
    new和delete的重载;placement new和placement delete
  • c++详解【new和delete

    万次阅读 多人点赞 2016-11-21 09:22:34
    说起new和delete,了解过c++的人应该都知道吧,它是用来分配内存释放内存的两个操作符。与c语言中的mallocfree类似。 c语言中使用malloc/calloc/realloc/free进行动态内存分配,malloc/calloc/realloc用来在堆...
  • 二维数组的new和delete

    千次阅读 2018-06-03 11:22:02
    int **p; p=new int*[N]; for(int i=0;i&lt;n;i++) p[i]=new int[N];... delete[]p[i];...道理很简单了,new和delete是反着来的,先new的最后delete,就构造函数析构函数的道理有些相似呢!...
  • c++中new和delete的使用方法

    千次阅读 2018-04-18 09:42:26
    new和delete运算符用于动态分配撤销内存的运算符new用法: 1. 开辟单变量地址空间 1)new int; //开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给...
  • C++ new和delete的原理分析

    千次阅读 2017-08-28 13:55:33
    前言Effective C++ rule 16规则,让我重新认识了deletenew在处理数组时候的方式。new 有两种形式的new,一种是生成一个对象的operator New,另一个是用于数组的operator new []。同时 operator delete也分普通版本的...
  • C++new和delete实现原理

    万次阅读 多人点赞 2014-09-01 02:01:36
    new操作 delete操作 new[]操作 delete[]操作
  • C++中new delete在类结构体的使用

    千次阅读 2019-04-18 23:42:19
    C++动态分配内存空间 : new delete:https://blog.csdn.net/m0_37153702/article/details/77845269 C++中的new与struct:https://blog.csdn.net/yongan1006/article/details/7715990
  • C++——new和delete之后发生了什么?

    千次阅读 2017-07-16 14:19:53
    总有一些事情比其他事情更基本一点,现在我来谈谈当我们new和delete之后到底发生了什么。C++中的五种内存在C++中内存分为五个区:堆、栈、自由存储区、全局/静态存储区常量存储区。 堆区:用户使
  • Cpp--new和delete的应用

    千次阅读 2016-03-08 19:09:29
    一.new的用法: 1. new() 分配这种类型的一个大小的内存空间,并以括号中的值来初始化这个变量; 2. new[] 分配这种类型的n个大小的内存空间,并用默认构造函数来初始化这些变量; 例子: #include #...
  • C++开发DLL中使用new和delete注意事项

    千次阅读 2016-11-11 18:26:33
    1,在 DLL 中用 new 来创建宿主程序中的对象,然后把这个对象指针保存到宿主程序,当 DLL 被卸载后,凡是涉及到这个指针的调用都会报错,包括 delete 这个指针也会有错。 2,在DLL中new出一个对象,然后在不需要...
  • 一、 new的原理 new简单类型直接调用operator new分配内存;而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小后调用operator new;对于复杂数据结构,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,214,272
精华内容 485,708
关键字:

new和delete