精华内容
下载资源
问答
  • 什么new/deletenew[]/delete[]必须配对使用?newdelete的内部机制这里不赘述了,戳这里《浅谈 C++ 中的 new/deletenew[]/delete[]》 glibc的mallc和free实现的内存分配释放简介,戳这里《malloc和free的...

    为什么new/delete和new[]/delete[]必须配对使用?

    new和delete的内部机制这里不赘述了,戳这里《浅谈 C++ 中的 new/delete 和 new[]/delete[]》
    glibc的mallc和free实现的内存分配释放简介,戳这里《malloc和free的内存到底有多大?——GNU glib库》
    第一篇博客讲的很好,但是最后new、delete的为什么配对使用的解释单纯理解还不到位。这里总结并补充说明一下。


    动态内存使用表

    分配方式删除方式结果结果分析
    newdelete成功合法
    newdelete[]失败参考下文Q1
    new[]delete内嵌类型成功;自定义类型失败参考下文Q2
    new[]delete[]成功合法

    合法的new/delete和mallc/free示意图

    这里写图片描述
    说明:如果对象按照字节对齐的话,那么对象之间可能存在填充,因此图中的对象间隔不一定存在。

    前提知识1:第一篇博客中已经阐明:new [] 时多分配 4 个字节,用于存储用户实际分配的对象个数。而new不会多分配。
    前提知识2:在第二篇博客中,介绍了mallc和free的实际大小,这个信息存储在内存块的块头里面。其中最重要的就是指示实际分配的内存大小(单位:字节),那么在free时,就要将用户的传入的地址,减去块头长度找到实际分配内存的起始地址然后释放掉。块头的长度是8字节。
    知道这两个前提知识下面两个问题就好解释了。

    Q1: new为什么使用delete[]失败?

    new时不会偏移4字节,delete[]时,编译器就是通过标识符[]而执行减4字节操作。从上图可知,减后的地址值会落到块头中,同时编译器从块头中提取4字节中的值作为自己执行析构对象的个数,而这4个字节是实际内存的长度,这是一个比较偏大的值,比如256,然后编译器对随后的内存对象执行析构,基本上都会导致内存越界,这必然失败。

    Q2:new[]为什么内嵌类型使用delete成功,自定义类型delete失败?

    new[],如果是内嵌类型,比如char或者int等等,就是C数组,那么它不会向后(地址变大的方向)偏移4个字节。因此执行delete时,显然不会出现任何问题。
    但是如果是自定义类型呢?那么new[]时就会向后偏移4个字节,从malloc的返回地址偏移4个字节用来存储对象个数,如果使用delete,编译器是识别不出释放的是数组,那么它会直接将传入对象的首地址值处执行一次对象析构,这个时候还不会出现问题,但是再进一步,它把对象的首地址值传递给free时,那么这个地址值并不是malloc返回的地址,而是相差了4个字节,此时free向前偏移取出malloc的实际长度时,就会取出对象的个数值作为实际的分配长度进行释放,显然这将导致只释放了n字节,其余的块头一部分和除n字节的所有内存都泄露了,并且只有第一个对象成功析构,其余都没有析构操作。一般对象个数n是个非常小的值,比如128个对象,那么free只释放了128字节。(注意:不同的libc实现不同,这里只示例阐述原理,不深究数字)
    这里写图片描述

    好了,解释清楚了。

    展开全文
  • 浅谈 C++ 中的 new/deletenew[]/delete[]

    万次阅读 多人点赞 2014-03-17 22:49:32
    在 C++ 中,你也许经常使用 newdelete 来动态...什么时候用它们?你知道 operator new 和 operator delete 吗?为什么 new [] 出来的数组有时可以用 delete 释放有时又不行?… 如果你对这些问题都有疑问的话,

    在 C++ 中,你也许经常使用 new 和 delete 来动态申请和释放内存,但你可曾想过以下问题呢?

    • new 和 delete 是函数吗?
    • new [] 和 delete [] 又是什么?什么时候用它们?
    • 你知道 operator new 和 operator delete 吗?
    • 为什么 new [] 出来的数组有时可以用 delete 释放有时又不行?

    如果你对这些问题都有疑问的话,不妨看看我这篇文章。

    new 和 delete 到底是什么?

    如果找工作的同学看一些面试的书,我相信都会遇到这样的题:sizeof 不是函数,然后举出一堆的理由来证明 sizeof 不是函数。在这里,和 sizeof 类似,new 和 delete 也不是函数,它们都是 C++ 定义的关键字,通过特定的语法可以组成表达式。和 sizeof 不同的是,sizeof 在编译时候就可以确定其返回值,new 和 delete 背后的机制则比较复杂。
    继续往下之前,请你想想你认为 new 应该要做些什么?也许你第一反应是,new 不就和 C 语言中的 malloc 函数一样嘛,就用来动态申请空间的。你答对了一半,看看下面语句:

    string *ps = new string("hello world");
    

    你就可以看出 new 和 malloc 还是有点不同的,malloc 申请完空间之后不会对内存进行必要的初始化,而 new 可以。所以 new expression 背后要做的事情不是你想象的那么简单。在我用实例来解释 new 背后的机制之前,你需要知道 operator new 和 operator delete 是什么玩意。

    operator new 和 operator delete

    这两个其实是 C++ 语言标准库的库函数,原型分别如下:

    void *operator new(size_t);     //allocate an object
    void *operator delete(void *);    //free an object
    
    void *operator new[](size_t);     //allocate an array
    void *operator delete[](void *);    //free an array
    

    后面两个你可以先不看,后面再介绍。前面两个均是 C++ 标准库函数,你可能会觉得这是函数吗?请不要怀疑,这就是函数!C++ Primer 一书上说这不是重载 new 和 delete 表达式(如 operator= 就是重载 = 操作符),因为 new 和 delete 是不允许重载的。但我还没搞清楚为什么要用 operator new 和 operator delete 来命名,比较费解。我们只要知道它们的意思就可以了,这两个函数和 C 语言中的 malloc 和 free 函数有点像了,都是用来申请和释放内存的,并且 operator new 申请内存之后不对内存进行初始化,直接返回申请内存的指针。

    我们可以直接在我们的程序中使用这几个函数。

    new 和 delete 背后机制

    知道上面两个函数之后,我们用一个实例来解释 new 和 delete 背后的机制:

    我们不用简单的 C++ 内置类型来举例,使用复杂一点的类类型,定义一个类 A:

    class A
    {
    public:
        A(int v) : var(v)
        {
            fopen_s(&file, "test", "r");
        }
        ~A()
        {
            fclose(file);
        }
    
    private:
        int var;
        FILE *file;
    };
    

    很简单,类 A 中有两个私有成员,有一个构造函数和一个析构函数,构造函数中初始化私有变量 var 以及打开一个文件,析构函数关闭打开的文件。

    我们使用

    class *pA = new A(10);
    

    来创建一个类的对象,返回其指针 pA。如下图所示 new 背后完成的工作:

    简单总结一下:

    1. 首先需要调用上面提到的 operator new 标准库函数,传入的参数为 class A 的大小,这里为 8 个字节,至于为什么是 8 个字节,你可以看看《深入 C++ 对象模型》一书,这里不做多解释。这样函数返回的是分配内存的起始地址,这里假设是 0x007da290。
    2. 上面分配的内存是未初始化的,也是未类型化的,第二步就在这一块原始的内存上对类对象进行初始化,调用的是相应的构造函数,这里是调用 A:A(10); 这个函数,从图中也可以看到对这块申请的内存进行了初始化,var=10, file 指向打开的文件
    3. 最后一步就是返回新分配并构造好的对象的指针,这里 pA 就指向 0x007da290 这块内存,pA 的类型为类 A 对象的指针。

    所有这三步,你都可以通过反汇编找到相应的汇编代码,在这里我就不列出了。

    好了,那么 delete 都干了什么呢?还是接着上面的例子,如果这时想释放掉申请的类的对象怎么办?当然我们可以使用下面的语句来完成:

    delete pA;
    

    delete 所做的事情如下图所示:

    delete 就做了两件事情:

    1. 调用 pA 指向对象的析构函数,对打开的文件进行关闭。
    2. 通过上面提到的标准库函数 operator delete 来释放该对象的内存,传入函数的参数为 pA 的值,也就是 0x007d290。

    好了,解释完了 new 和 delete 背后所做的事情了,是不是觉得也很简单?不就多了一个构造函数和析构函数的调用嘛。

    如何申请和释放一个数组?

    我们经常要用到动态分配一个数组,也许是这样的:

    string *psa = new string[10];      //array of 10 empty strings
    int *pia = new int[10];           //array of 10 uninitialized ints
    

    上面在申请一个数组时都用到了 new [] 这个表达式来完成,按照我们上面讲到的 new 和 delete 知识,第一个数组是 string 类型,分配了保存对象的内存空间之后,将调用 string 类型的默认构造函数依次初始化数组中每个元素;第二个是申请具有内置类型的数组,分配了存储 10 个 int 对象的内存空间,但并没有初始化。

    如果我们想释放空间了,可以用下面两条语句:

    delete [] psa;
    delete [] pia;
    

    都用到 delete [] 表达式,注意这地方的 [] 一般情况下不能漏掉!我们也可以想象这两个语句分别干了什么:第一个对 10 个 string 对象分别调用析构函数,然后再释放掉为对象分配的所有内存空间;第二个因为是内置类型不存在析构函数,直接释放为 10 个 int 型分配的所有内存空间。

    这里对于第一种情况就有一个问题了:我们如何知道 psa 指向对象的数组的大小?怎么知道调用几次析构函数?

    这个问题直接导致我们需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

    还是用图来说明比较清楚,我们定义了一个类 A,但不具体描述类的内容,这个类中有显示的构造函数、析构函数等。那么 当我们调用

    class A *pAa = new A[3];
    

    时需要做的事情如下:

    从这个图中我们可以看到申请时在数组对象的上面还多分配了 4 个字节用来保存数组的大小,但是最终返回的是对象数组的指针,而不是所有分配空间的起始地址。

    这样的话,释放就很简单了:

    delete [] pAa;
    

    这里要注意的两点是:

    • 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
    • 传入 operator delete[] 函数的参数不是数组对象的指针 pAa,而是 pAa 的值减 4。

    为什么 new/delete 、new []/delete[] 要配对使用?

    其实说了这么多,还没到我写这篇文章的最原始意图。从上面解释的你应该懂了 new/delete、new[]/delete[] 的工作原理了,因为它们之间有差别,所以需要配对使用。但偏偏问题不是这么简单,这也是我遇到的问题,如下这段代码:

    int *pia = new int[10];
    delete []pia;
    

    这肯定是没问题的,但如果把 delete []pia; 换成 delete pia; 的话,会出问题吗?

    这就涉及到上面一节没提到的问题了。上面我提到了在 new [] 时多分配 4 个字节的缘由,因为析构时需要知道数组的大小,但如果不调用析构函数呢(如内置类型,这里的 int 数组)?我们在 new [] 时就没必要多分配那 4 个字节, delete [] 时直接到第二步释放为 int 数组分配的空间。如果这里使用 delete pia;那么将会调用 operator delete 函数,传入的参数是分配给数组的起始地址,所做的事情就是释放掉这块内存空间。不存在问题的。

    这里说的使用 new [] 用 delete 来释放对象的提前是:对象的类型是内置类型或者是无自定义的析构函数的类类型!

    我们看看如果是带有自定义析构函数的类类型,用 new [] 来创建类对象数组,而用 delete 来释放会发生什么?用上面的例子来说明:

    class A *pAa = new class A[3];
    delete pAa;
    

    那么 delete pAa; 做了两件事:

    • 调用一次 pAa 指向的对象的析构函数;
    • 调用 operator delete(pAa); 释放内存。

    显然,这里只对数组的第一个类对象调用了析构函数,后面的两个对象均没调用析构函数,如果类对象中申请了大量的内存需要在析构函数中释放,而你却在销毁数组对象时少调用了析构函数,这会造成内存泄漏。

    上面的问题你如果说没关系的话,那么第二点就是致命的了!直接释放 pAa 指向的内存空间,这个总是会造成严重的段错误,程序必然会奔溃!因为分配的空间的起始地址是 pAa 指向的地方减去 4 个字节的地方。你应该传入参数设为那个地址!

    同理,你可以分析如果使用 new 来分配,用 delete [] 来释放会出现什么问题?是不是总会导致程序错误?

    总的来说,记住一点即可:new/delete、new[]/delete[] 要配套使用总是没错的!

    参考资料:

    C++ Primer 第四版

    展开全文
  • 在vs2015里,有这么一个文件 vcruntime_new.h 内容大致如下: #define __CRTDECL __cdecl  namespace std  {  struct nothrow_t { };  extern nothrow_t const nothrow;  }    _Ret_not...
    在vs2015里,有这么一个文件 vcruntime_new.h
    内容大致如下:

    #define __CRTDECL __cdecl

        namespace std
        {
            struct nothrow_t { };

            extern nothrow_t const nothrow;
        }
     
     _Ret_notnull_ void* operator new[](
        size_t _Size
        );
     
     _Ret_notnull_ void* operator new(
        size_t _Size
        );
     
       _Ret_maybenull_ void* operator new[](
     size_t                _Size,
        std::nothrow_t const&
        ) throw();
     
     _Ret_maybenull_ void* operator new(
        size_t                _Size,
        std::nothrow_t const&
        ) throw();
     
     void __CRTDECL operator delete(
     void* _Block
        ) throw();

     void __CRTDECL operator delete(
        void* _Block,
        std::nothrow_t const&
        ) throw();

     void __CRTDECL operator delete[](
        void* _Block
        ) throw();

     void __CRTDECL operator delete[](
        void* _Block,
        std::nothrow_t const&
        ) throw();

     void __CRTDECL operator delete(
        void*  _Block,
        size_t _Size
        ) throw();

     void __CRTDECL operator delete[](
        void* _Block,
        size_t _Size
        ) throw();

     #ifndef __PLACEMENT_NEW_INLINE
        #define __PLACEMENT_NEW_INLINE
        _Ret_notnull_
        inline void* __CRTDECL operator new(size_t _Size, void* _Where) throw()
        {
            (void)_Size;
            return _Where;
        }

        inline void __CRTDECL operator delete(void*, void*) throw()
        {
            return;
        }

        _Ret_notnull_
     inline void* __CRTDECL operator new[](size_t _Size,void* _Where) throw()
     {
      (void)_Size;
      return _Where;
     }

        inline void __CRTDECL operator delete[](void*, void*) throw()
        {
        }


    _Ret_maybenull_ 告诉你 null   maybe 不知道想表达什么??
    _Ret_notnull_   告诉你返回值为非 null
    throw()      告诉你这个函数不抛出异常

    我们先来分析一下分配内存的函数,这两个函数在分配失败时,抛出bad_alloc异常。
    通过调试追踪,两个函数的实现如下:

    #define SIZE_MAX 0FFFFFFFFh

    void* __CRTDECL operator new[](size_t const size)  //new_array.cpp
    {
     return operator new(size);
    }

    void* __CRTDECL operator new(size_t const size)    //new_scalar.cpp
    {
        for (;;)
        {
            if (void* const block = malloc(size))
            {
                return block;
            }

            if (_callnewh(size) == 0)
            {
                if (size == SIZE_MAX)
                {
                    __scrt_throw_std_bad_array_new_length();
                }
                else
                {
                    __scrt_throw_std_bad_alloc();
                }
            }

            // The new handler was successful; try to allocate again...
        }
    }

    msdn:
    int _callnewh( 
       size_t size 
       ) 
    _callnewh 调用当前已经安装的 “new handler” (函数,通过set_new_handle 设置)
    参数 size:  new operator 尝试去分配的内存数量
    返回值:
      失败,返回0,没有"new handler"安装 或者 没有"new handler"处于活跃状态
      成功,返回1,“new handler"已经安装并处于活跃状态。内存分配可以重试
    异常:此函数将抛出 bad_alloc异常,如果不能找到 "new handler"(已经安装)
    备注:如果new 运算符无法成功分配内存,"new handle"函数可以启动一些适当的操作。
          如释放内存,以便后续分配成功。
    ok,_callnewh和_set_new_handler相关,具体怎样, msdn 有具体的例子,很容易理解。
    ok,通过以下代码的调试跟踪:
    #include <stdio.h>
    #include <new.h>
    void main()
    {
     void* pVoid = ::operator new(SIZE_MAX);
    }
    我发现:_callnewh返回了0。所以我们得
     看看vs在void main函数之前是否插入了安装"new handler"函数的代码
    #include <stdio.h>
    #include <new.h>
    void main()
    {
     _PNH pnh = _set_new_handler(0);
     printf("%d\n",(int)pnh);
    }
    输出 0,说明没有安装

    ok,这就很好理解了。如果在我们调用 operator new 之前,都没有调用_set_new_handle,
    去安装 处理函数(比如释放内存啊)去处理malloc 失败,那么 _callnewh函数将返回0,
    接着就是处理 size 是否等于 SIZE_MAX(在这里 SIZE_MAX == 0x0FFFFFFFF),如果是则抛出
    bad_array_new_length 异常,否则抛出 bad_alloc异常.

    所以 operator new(size_t Size) 分配失败是抛出异常的。

    所以下面这样写代码是错误的:
    #include <stdio.h>
    #include <new.h>
    void main()
    {
     void* pVoid = ::operator new(SIZE_MAX); //抛出异常
     if(pVoid == nullptr)     //永远也执行不到这里
      printf("opeartor new faile\n"); 
    }
    #include <stdio.h>
    #include <new.h>
    void main()
    {
     try
     {
      void* pVoid = ::operator new(SIZE_MAX);
     }catch(bad_alloc &ba) //bad_array_new_length是bad_alloc的子类
     {
      printf("%s\n",ba.what());
     }
    }

    ok ! 顺便贴一下相关的异常类源码,便于以后阅读:


    //这两函数是导入函数,从exception,来看,这两个函数是分配内存,和销毁内存,
    //因为std::exception 是处理内存无法分配的异常,所以,他的内存分配机制肯定
    //不同,源码没有公布出来。
    __declspec(dllimport) void __cdecl __std_exception_copy(
        _In_  __std_exception_data const* _From,
        _Out_ __std_exception_data*       _To
        );

    __declspec(dllimport) void __cdecl __std_exception_destroy(
        _Inout_ __std_exception_data* _Data
        );
    ///
    /// vcruntime_exception.h start///
    struct __std_exception_data
    {
        char const* _What;
        bool        _DoFree;
    };

    namespace std {

     class exception
     {
     public:
      exception() throw()
       : _Data()
      {
      }

      explicit exception(char const* const _Message) throw()
       : _Data()
      {
       __std_exception_data _InitData = { _Message, true };
       __std_exception_copy(&_InitData, &_Data);
      }

      exception(char const* const _Message, int) throw()
       : _Data()
      {
       _Data._What = _Message;
      }

      exception(exception const& _Other) throw()
       : _Data()
      {
       __std_exception_copy(&_Other._Data, &_Data);
      }

      exception& operator=(exception const& _Other) throw()
      {
       if (this == &_Other)
       {
        return *this;
       }
       __std_exception_destroy(&_Data);
       __std_exception_copy(&_Other._Data, &_Data);
       return *this;
      }

      virtual ~exception() throw()
      {
       __std_exception_destroy(&_Data);
      }

      virtual char const* what() const
      {
       return _Data._What ? _Data._What : "Unknown exception";
      }
     private:
      __std_exception_data _Data;
     };

     class bad_exception
      : public exception
     {
     public:
      bad_exception() throw()
       : exception("bad exception", 1)
      {
      }
     };

     class bad_alloc
      : public exception
     {
     public:
        bad_alloc() throw()
            : exception("bad allocation", 1)
        {
        }
     private:
      friend class bad_array_new_length;

      bad_alloc(char const* const _Message) throw()
       : exception(_Message, 1)
      {
      }
     };

     class bad_array_new_length
      : public bad_alloc
     {
     public:

      bad_array_new_length() throw()
       : bad_alloc("bad array new length")
      {
      }
     };
    }
    /// vcruntime_exception.h end///

    ok ,看源码,就很容易理解了。

    现在我们来看一下,另外两个能分配内存的函数,这两个函数
    分配失败时,返回nullptr ,不抛出异常.

    //new_array_nothrow.cpp
    void* __CRTDECL operator new[](size_t const size, std::nothrow_t const& x) noexcept
    {
        return operator new(size, x);
    }

    //new_scalar_nothrow.cpp
    void* __CRTDECL operator new(size_t const size, std::nothrow_t const&) noexcept
    {
        try
        {
            return operator new(size);
        }
        catch (...)
        {
            return nullptr;
        }
    }

    相当简单,他们最终调用的是 operator new(size_t _Size),
    然后捕获 operator new(size_t _Size)异常,
    如果 operator new(size_t _Size)分配失败,返回 nullptr;

    ok! 现在来分析删除操作的函数
    //delete_scalar.cpp
    void __CRTDECL operator delete(void* const block) noexcept
    {
        #ifdef _DEBUG
        _free_dbg(block, _UNKNOWN_BLOCK);
        #else
        free(block);               //release版本,直接调用 free ,非常简单
        #endif
    }

    //delete_scalar_size.cpp
    void __CRTDECL operator delete(void* block, size_t) noexcept
    {
        operator delete(block);
    }
     
    void __CRTDECL operator delete(
        void* _Block,
        std::nothrow_t const&
        ) throw();  //并未实现,直接调用 opeartor delete(void* block,size_t)

    //delete_array.cpp
    void __CRTDECL operator delete[](void* block) noexcept
    {
        operator delete(block);
    }

    //delete_array_size.cpp
    void __CRTDECL operator delete[](void* block, size_t) noexcept
    {
        operator delete[](block);
    }

    //delete_array_nothrow.cpp
    void __CRTDECL operator delete[](void* block, std::nothrow_t const&) noexcept
    {
        operator delete[](block);
    }

    很简单,最终调用的都是free函数,出现那么多 operator delete 的重载函数,主要
    是与 operator new 对应。

    operator new delete new[] delete[] 是可以重载的。

    那就纳闷了, new delete 不是会调用构造函数,虚构函数吗? new X[size] 不是要用 delete[] 来释放吗
    楼主,你分析有误吧。怎么没看到构造函数,虚构函数的调用,怎么 delete[] delete 都只是调用了free.
    嗯,其实 new delete new X[size] delete[] 是编译器的关键字,只有少部分的人能改变他的操作方式。
    我用的是VS,所以只有VS的编写者,能改变。
    而  operator new delete new[] delete[] 注意" operator "  ,只负责如何去分配内存。所以我们是可以
    通过重载来改变的。

    我们通过反汇编来,区分它们的区别
    #include <stdio.h>
    #include <new.h>

    class testClass
    {
    public:
     testClass(int iValue = 0)
     {
      m_iValue = iValue;
      printf("testClass::testClass()\n");
     }

     ~testClass()
     {
      printf("testClass::~testClass()\n");
     }

     void testFunc()
     {
      printf("testClass::testFunc() m_iValue = %d\n", m_iValue);
     }

    private:
     int m_iValue;
    };

    void main()
    {
     testClass* ptc = new testClass(1);
     testClass* ptc1 = (testClass*)::operator new(sizeof(testClass));
     delete ptc; 
     ::operator delete(ptc1);
    }    

    看一下反汇编代码:
     testClass* ptc = new testClass(1);
    //调用 operator new(size_t _Size) 分配内存 
    00A91076  push        4 
    00A91078  call        operator new (0A910CFh)
    //调用 testClass::testClass(int) 构造函数
    00A9107D  mov         edi,eax 
    00A9107F  push        offset string "testClass::testClass()\n" (0A92190h) 
    00A91084  mov         dword ptr [ebp-4],edi 
    00A91087  mov         dword ptr [edi],1 
    00A9108D  call        printf (0A91040h) 

     testClass* ptc1 = (testClass*)::operator new(sizeof(testClass));
    //调用 operator new(size_t _Size) 分配内存 
    00A91092  push        4 
    00A91094  call        operator new (0A910CFh)
     
     delete ptc;
    //调用 testClass::~testClass() 析构函数
    00A91099  push        offset string "testClass::~testClass()\n" (0A921A8h) 
    00A9109E  mov         esi,eax 
    00A910A0  call        printf (0A91040h) 
    // 调用 operator delete(void* block,size_t Size) 释放内存
    00A910A5  push        4 
    00A910A7  push        edi  //edi 为 ptc
    00A910A8  call        operator delete (0A9136Ah)
     
     ::operator delete(ptc1);
    //调用 operator delete(void* bolck) 释放内存
    00A910AD  push        esi  // esi 为ptc1
    00A910AE  call        operator delete (0A91378h) 
    00A910B3  add         esp,1Ch

    operator delete(void* block)  和 operator delete(void* block,size_t size) 最终都调用free 没什么区别
    很容易理解。

    void main()
    {
     testClass* ptc_a = new testClass[6];
     testClass* ptc1_a = (testClass*)::operator new[](sizeof(testClass)*6);
     delete[] ptc_a;
     ::operator delete[](ptc1_a);
    }
    反汇编:
     testClass* ptc_a = new testClass[6];
    //调用 operator new[](size_t size) --> operator new(size_t size) 分配内存
    003810A6  push        1Ch   //编译器计算所得内存大小 sizeof(testClass)*6 = 4*6 = 24 = 18h
                                // 18h + 4(保存数组个数 6) = 1ch
    003810A8  call        operator new[] (0381506h) 
    003810AD  add         esp,4 
    //调用testClass::testClass() 构造函数 (循环6次)
    003810B0  mov         dword ptr [ebp-10h],eax 
    003810B3  mov         dword ptr [ebp-4],0 
    003810BA  test        eax,eax 
    003810BC  je          main+5Dh (03810DDh) 
    003810BE  push        offset testClass::~testClass (0381070h) 
    003810C3  push        offset testClass::`default constructor closure' (0381140h) 
    003810C8  push        6  //循环6次
    003810CA  lea         edi,[eax+4] 
    003810CD  mov         dword ptr [eax],6  //保存数组个数
    003810D3  push        4 
    003810D5  push        edi 
    003810D6  call        `eh vector constructor iterator' (038150Fh)
    003810DB  jmp         main+5Fh (03810DFh) 
    003810DD  xor         edi,edi 

     testClass* ptc1_a = (testClass*)::operator new[](sizeof(testClass)*6);
    //调用 operator new[](size_t size) 分配内存 
    003810DF  push        18h   //sizeof(testClass)*6 = 4*6 = 24 = 18h
    003810E1  mov         dword ptr [ebp-4],0FFFFFFFFh 
    003810E8  call        operator new[] (0381506h) 
    003810ED  add         esp,4 
    003810F0  mov         ebx,eax 

     delete[] ptc_a;
    //调用 testClass::~testClass() (循环6次)
    003810F2  test        edi,edi 
    003810F4  je          main+0A2h (0381122h) 
    003810F6  push        offset testClass::~testClass (0381070h) 
    003810FB  mov         dword ptr [ebp-4],1 
    00381102  lea         esi,[edi-4] 
    00381105  push        dword ptr [esi]   //压入数组个数 6
    00381107  push        4 
    00381109  push        edi 
    0038110A  call        `eh vector destructor iterator' (038141Fh) 
    //调用 operator delete[](void*,size_t) 释放内存
    0038110F  mov         eax,dword ptr [esi] 
    00381111  lea         eax,[eax*4+4] 
    00381118  push        eax 
    00381119  push        esi 
    0038111A  call        operator delete[] (0381411h) 
    0038111F  add         esp,8
     
     ::operator delete[](ptc1_a);
    //调用 operator delete[](void*) 释放内存
    00381122  push        ebx 
    00381123  call        operator delete[] (038140Ch) 
    00381128  add         esp,4 
     
    ok,现在明白了吧。new delete 是编译器的关键字
    new 先调用 ::operator new 分配内存,然后以分配的内存为 this调用构造函数
    delete 先调用 析构函数,再调用 ::operator delete 释放内存
    new X[size] 先调用 ::operator new[] 分配内存,在调用每个元素的构造函数
    delete[] X  先调用 每个元素的析构函数,再调用 ::operator delete[] 释放内存
    前面看过 ::operator new ::operator new[] 源码,其实都是调用 malloc 分配内存
             ::operator delete  ::operator delete[] 都是调用 free 释放内存

    ok, 来看一下怎么来重载 这些操作符;

    #include <stdio.h>
    #include <stdlib.h>
    #include <new.h>

    class testClass
    {
    public:
     testClass(int iValue = 0)
     {
      m_iValue = iValue;
      printf("testClass::testClass()\n");
     }

     ~testClass()
     {
      printf("testClass::~testClass()\n");
     }

     void testFunc()
     {
      printf("testClass::testFunc() m_iValue = %d\n", m_iValue);
     }

     void* operator new(size_t Size)
     {
      printf("testClass::operator new(size_t)\n");
      return malloc(Size);
     }

     void operator delete(void* pblock)
     {
      printf("testClass::operator delete(void*)\n");
      if (nullptr != pblock)
       free(pblock);
     }

     void* operator new[](size_t Size)
     {
      printf("testClass::operator new[](size_t)\n");
      return malloc(Size);
     }

     void operator delete[](void* pblock)
     {
      printf("testClass::operator delete[](void*)\n");
      if (nullptr != pblock)
       free(pblock);
     }
    private:
     int m_iValue;
    };

    void main()
    {
     testClass* ptc = new testClass(1);
     delete ptc;
     testClass* ptc_a = new testClass[6];
     delete[] ptc_a;
    }    

    反汇编:
     testClass* ptc = new testClass(1);
    //调用重载 operator new(size_t) 分配内存
    009010C5  push        offset string "testClass::operator new(size_t)\n"... (090214Ch) 
    009010CA  call        printf (0901040h) 
    009010CF  mov         edi,dword ptr [__imp__malloc (0902054h)] 
    009010D5  push        4 
    009010D7  call        edi 
    009010D9  mov         esi,eax 
    //调用构造函数
    009010DB  add         esp,8 
    009010DE  mov         dword ptr [ebp-10h],esi 
    009010E1  test        esi,esi 
    009010E3  je          main+73h (0901113h) 
    009010E5  push        offset string "testClass::testClass()\n" (0902118h) 
    009010EA  mov         dword ptr [esi],1 
    009010F0  call        printf (0901040h) 

     delete ptc;
    //调用析构函数
    009010F5  push        offset string "testClass::~testClass()\n" (0902130h) 
    009010FA  call        printf (0901040h) 
    //调用重载 operator delete(void*) 释放内存
    009010FF  push        offset string "testClass::operator delete(void*"... (0902170h) 
    00901104  call        printf (0901040h) 
    00901109  push        esi 
    0090110A  call        dword ptr [__imp__free (0902058h)] 
    00901110  add         esp,10h 

     testClass* ptc_a = new testClass[6];
    //调用重载 operator new[](size_t)分配内存
    00901113  push        offset string "testClass::operator new[](size_t"... (0902194h) 
    00901118  call        printf (0901040h) 
    0090111D  push        1Ch 
    0090111F  call        edi 
    00901121  add         esp,8 
    00901124  mov         dword ptr [ptc],eax 
    00901127  mov         dword ptr [ebp-4],0 
    0090112E  test        eax,eax 
    00901130  je          main+0B1h (0901151h)
    //调用每个元素的构造函数 
    00901132  push        offset testClass::~testClass (0901070h) 
    00901137  push        offset testClass::`default constructor closure' (09011B0h) 
    0090113C  push        6 
    0090113E  lea         esi,[eax+4] 
    00901141  mov         dword ptr [eax],6 
    00901147  push        4 
    00901149  push        esi 
    0090114A  call        `eh vector constructor iterator' (0901530h) 
    0090114F  jmp         main+0B3h (0901153h) 
    00901151  xor         esi,esi 
    00901153  mov         dword ptr [ebp-4],0FFFFFFFFh 

     delete[] ptc_a;
    //调用每个元素的析构函数
    0090115A  test        esi,esi 
    0090115C  je          main+0F2h (0901192h) 
    0090115E  push        offset testClass::~testClass (0901070h) 
    00901163  mov         dword ptr [ebp-4],1 
    0090116A  lea         edi,[esi-4] 
    0090116D  push        dword ptr [edi] 
    0090116F  push        4 
    00901171  push        esi 
    00901172  call        `eh vector destructor iterator' (0901449h) 
    //调用重载函数 operator delete[](void*) 释放内存
    00901177  push        offset string "testClass::operator delete[](voi"... (09021B8h) 
    0090117C  call        printf (0901040h) 
    00901181  add         esp,4 
    00901184  test        edi,edi 
    00901186  je          main+0F2h (0901192h) 
    00901188  push        edi 
    00901189  call        dword ptr [__imp__free (0902058h)] 
    0090118F  add         esp,4 
    }

    new   new X[size]  后者需要为每个元素调用构造函数 ,后者还需要多分配4字节的内存保存数组大小
    delete  delete[] X   后者需要为每个元素调用析构函数,delete  delete[] 绝对不是 调用虚构函数的
                         次数的差别,因为以new X[size] 分配的内存的前4个字节是保存数组的大小。所以
          new 分配的用 delete 删除, new X[size] 用 delete[] X 删除, ok !

    下面是 placement new delete 的实现,很简单.就不多说了.使用 placement new 需要<new.h>
     #ifndef __PLACEMENT_NEW_INLINE
        #define __PLACEMENT_NEW_INLINE
        _Ret_notnull_
        inline void* __CRTDECL operator new(size_t _Size, void* _Where) throw()
        {
            (void)_Size;
            return _Where;
        }

        inline void __CRTDECL operator delete(void*, void*) throw()
        {
            return;
        }

        _Ret_notnull_
     inline void* __CRTDECL operator new[](size_t _Size,void* _Where) throw()
     {
      (void)_Size;
      return _Where;
     }

        inline void __CRTDECL operator delete[](void*, void*) throw()
        {
        }
     

    除了placement new delete new[] delete[]其他的 operator new delete new[] delete[]都可以重载
    我还有一篇文章是讲解 placement new 的。
    展开全文
  • 一、什么时候new[]申请,可以用delete释放 1、在C++中,把单个元素的内存开辟释放和数组的内存开辟释放是分开的。因为在C++里面,开辟内存和构造对象是一起发生的,析构对象和释放内存是一起发生的 int *a = ...

    一、什么时候用new[]申请,可以用delete释放

      1、在C++中,把单个元素的内存开辟释放和数组的内存开辟释放是分开的。因为在C++里面,开辟内存和构造对象是一起发生的,析构对象和释放内存是一起发生的

    int *a = new int;  //delete a 或 delete[] 都可以
    int *p = new int[]; //delete p 或 delete p[] 都可以

    也就是说上述两种情况的释放效果相同,原因在于它们是内置类型,内置类型是没有所谓的构造和析构的。对于内置类型来说,内存大小已经确定,系统可以记忆并且进行管理,在析构的时候,系统并不会调用析构函数,它直接通过指针可以获取实际分配的内存空间

    2、对于没有析构函数的自定义类类型

    class Test
    {
        public:
            Test(){}
        private:
             int _data;
    };
    
    Test *p = new Test; delete p;
    
    Test *p = new Test; delete []p;
    
    Test *p = new Test[10]; delete []p;
    
    Test *p = new Test[10]; delete p;
    

    上述的4条代码也是不存在任何问题的,因为这个类没有提供析构函数,也就是说Test类对象不需要进行任何有效的析构,那么delete就只剩完成free的功能就行了

    总结:综上两点我们可以得出结论,在使用new开辟内存的时候,如果是内置类型或者是自定义类类型没有提供析构函数的情况下,用new[]分配的内存空间可以使用delete和delete[]两种释放内存方式来释放

    3、

    class Test
    {
        public:
            Test(){}
           ~Test(){}
        private:
             int _data;
    };
    
    Test *p = new Test; delete p;  //ok
    
    Test *p = new Test; delete []p; //程序崩溃
    
    Test *p = new Test[10]; delete []p;//ok
    
    Test *p = new Test[10]; delete p; //程序崩溃
    

    此时有了自定义的析构函数我们对上面的四条代码进行分析

    Test *p = new Test; delete []p; //程序崩溃
    

    这段代码在内存上开辟了4字节的内存,如下:

    而delete[]释放代码的位置其实是下面这张图:

    为什么它为什么会减去4个字节开始释放内存呢?因为定义了析构函数,在释放内存在前需要在内存中析构对象,你写个delete[]p,编译器就认为这里有很多对象要析构,有多少个呢?记录对象个数的数字就在对象内存上面的4个字节存放,因此它从第二幅图的那个位置开始释放,如果按第一个图的代码写肯定是要发生错误的

    下面这段代码的内存分配

    Test *p = new Test[10]; delete p; //程序崩溃
    

    这样错误就一目了然了吧

    总结:(delete和delete[]的区别)

    • delete p;  //仅释放了p指针指向的全部内存空间,但是只调用了p[0]对象的析构函数,剩下的从p[1]到p[9]这9个用户自行分配的对应内存空间将不能释放,从而造成内存泄漏
    • delete []p;//调用使用类对象的析构函数释放自己分配内存空间,并且释放了p指针指向的全部内存空间(逐一释放数组中每个对象的内存)
    展开全文
  • 在 C++ 中,你也许经常使用 newdelete 来动态...什么时候用它们?你知道 operator new 和 operator delete 吗?为什么 new [] 出来的数组有时可以用 delete 释放有时又不行?… 如果你对这些问题都有疑问的话,
  • 什么时候delete[]和delete

    千次阅读 2017-09-06 09:36:53
    delete 释放new分配的单个对象指针指向的内存 delete[] 释放new分配的对象数组指针指向的内存 当new对象有[]就用delete[],否则用deletedelete指针以后应赋值为NULL,最好先判断这个指针是否存在 例如char* ...
  • 首先想到的是c语言中的 **malloc** 和 **free** ,这两个是库函数,而在c++中 newdelete是运算符(和 + - * /一样),在使用new的时可以看成使用默认使用构造函数+malloc,使用delete时可以堪称使用析构函数+...
  • 注:以下测试均在VS2015下进行,...二、operator new/delete,以及其对应的数组版本new[]/delete[]。 operator new优先调用全局重载的,所以::operator new(sizeof(COperatorTest))和operator new(sizeof(COper
  • 关于C++中new/deletenew[]/delete[]

    千次阅读 2018-10-08 14:20:43
    浅谈 C++ 中的 new/deletenew[]/delete[] operator new 和 operator delete 这两个其实是 C++ 语言标准库的库函数,原型分别如下: void *operator new(size_t); //allocate an object void *operator delete...
  • 最近在为一个Base项目实现需求,看到了大量的重载operator new/delete的轮子代码,而这方面之前并没有过多去关注,于是花了点时间,查一查资料,写了写Demo的代码,总算是对C++中new/delete这一家族的成员有了个了解...
  • Qt在什么情况下new完以后不需要delete

    千次阅读 2019-07-04 21:04:17
    Qt提供了类对象析构的简化机制,即QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象。 详细参考可见: https://blog.csdn.net/lihaidong1991/article/details/79388810 ...
  • 重载operator new/delete/new[]/delete[]

    千次阅读 2018-10-06 12:25:45
    Abstact 之前看侯捷老师的C++面向对象开发上。老师讲了一个知识点,如下图: 在他的C++面向对象高级编程(下)课程中,侯捷老师讲了重载operator new,operator delete。...重载了operator new/delete/new[]/delet...
  • 原文:... C++中的new/delete与operator new/operator delete new operator/delete operator就是newdelete操作符,而operator new/operator delete是函数。
  • C++ newdelete

    千次阅读 多人点赞 2020-05-07 20:23:14
    在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。...在cpp中,这两个函数仍然可以使用,但是C++又新增了两个关键字,newdeletenew 用来动态分配内存,delete 用来释放内...
  • 【C++】动态内存分配详解(new/new[]和delete/delete[])

    万次阅读 多人点赞 2018-09-07 13:14:42
    一、为什么需要动态内存分配?... 在这些情况下,程序需要动态分配内存,C ++语言将运算符newdelete合成在一起。 (1)特点 1.C++中通过new关键字进行动态内存申请 2.C++中的动态内存分配是基于类型进行的 ...
  • 一:malloc/free是c/c++中的标准库函数,new/delete是c++中的运算符。它们都用于申请动态内存和释放内存。二:对于非内部数据对象(eg:类对象),只用malloc/free无法满足动态对象的要求。这是因为对象在创建的同时...
  • 在使用new时候做了两件事: 1、调用operator new分配空间 2、调用构造函数初始化对象 在使用delete时候也做了两件事: 1、调用析构函数清理对象 2、调用operator delete函数释放空间 在使用new[N]...
  • newdelete底层解析

    千次阅读 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的关系 先写个栗子看...
  • new/deletenew[]/delete[]实现机制

    千次阅读 2017-02-18 21:48:10
    newdelete是C++新引入的单目运算符,它们可以从堆上分配和删除存储块(堆在C++中也叫自由存储).用new运算符(不是函数)时要指明数据类型,以后new就分配一个足以放下指明类型对象的存储,并返回该存储块的首地址作为...
  • new/delete 详解

    万次阅读 多人点赞 2016-05-18 14:31:12
    newdelete 是 C++ 用于管理 堆内存</font> 的两个运算符,对应于C语言中的 malloc 和 free,但是malloc和free是函数,newdelete 是运算符。
  • newdelete用法

    千次阅读 2017-03-06 13:53:30
    new以下情况可以使用 1 申请对象数量不确定时 2 需要空间较大时 在 C++ 中,你也许经常使用 newdelete 来动态申请和...什么时候用它们?你知道 operator new 和 operator delete 吗?为什么 new [] 出来的数
  • C++ newdelete的原理分析

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

    万次阅读 多人点赞 2018-06-12 21:27:36
    C++ 中的创建和删除数组(new/deletenew[]/delete[]) 已有 4122 次阅读 2017-4-17 16:33|个人分类:C++|系统分类:科研笔记|关键词:C++,数组,动态数组|数组, 动态数组 ...
  • 关于newdeletenew[] 和delete[]

    千次阅读 2010-03-12 11:14:00
    通常状况下,编译器在new时候会返回用户申请的内存空间大小,但是实际上,编译器会分配更大的空间,目的就是在delete时候能够准确的释放这段空间。这段空间在用户取得的指针之前以及用户空间末尾之后存放。实际...
  • C++经典面试题 | new/new[]和delete/delete[]的区别原理

    千次阅读 多人点赞 2019-04-15 13:58:39
    在C++中堆内存的分配和释放是通过newdelete来操作的,它们和C语言的malloc和free有什么区别呢? 1)new的底层也是通过malloc来开辟内存的,new比malloc多一项功能,就是开辟完内存,还可以进行初始化操作,如下: ...
  • newdelete的底层实现原理

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

    千次阅读 2008-11-28 17:58:00
    在C#、Java這種managed語言,因為有garbage collection,所以完全不用考慮free()或delete,但在C/C++,有時候要delete的,有時又不用,到底哪些改delete?哪些不用delete呢?簡單的說,若要使用用到heap,就要手動去...
  • 【C++札记】newdelete

    千次阅读 2017-07-17 14:24:32
    1.malloc,free和new,delete区别。 a.malloc,free是C/C++的标准库函数。new,delete是c++的操作符。 b.malloc申请的是内存,严格意义不是“对象”,new申请的可以理解为“对象”,new 时会调用构造函数,返回指向...
  • c++ new delete new[] delete[] 底层实现

    千次阅读 2017-01-17 18:01:16
    关于deletedelete[]的相关实现。
  • 浅谈 C++ 中的 new/delete

    千次阅读 2016-03-17 21:31:37
    newdeletenewdelete 到底是什么?如果找工作的同学看一些面试的书,我相信都会遇到这样的题: sizeof不是函数,然后举出一堆的理由来证明 sizeof 不是函数。在这里,和sizeof类似,newdelete也不是函数,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,199,753
精华内容 479,901
关键字:

new什么时候需要delete