Something More about “new” &”delete” in C++

一、不只是malloc和free

   new和delete都是C++中最基本的词汇,每天的代码都反复的与他们打交道,对他们最基本的认识就是分配和释放内存,最开始我认为他们就是C++版的malloc和free,但是其实内有玄机,除了分配内存,还可以用他们实现什么?

 

 二、new和delete的完整过程

     2.1    new  的完整过程可以分为三步,拿A* a=new A(1)这句为例:

  • 首先它将调用operator new(size_t size),在内存中分配sizeof(A)这么大的内存,也就是参数size=sizeof(A);

                operator new(size_t size)的默认实现是:

                    void *ptr = (void *)malloc(size);
                     return ptr;           

  • 第二步调用operator new( size_t count, void *
    object
    ),这个函数会忽略第一个参数,在已经分配好的内存object上返回A需要的那篇内存            

               operator new( size_t count, void *
object)的默认实现是                  

                  return object;

  • 第三步调用A的构造函数产生A

   

        前两个步骤其实是operator new的不同overwrite,可能开始不太容易理解第二步的意义,第二步好像什么都没做,为什么还要存在这种形式的operator new呢?其实是因为这些operator new都可以被程序访问,例如我们可以直接写出这样的代码完成new一样的操作:

      void * raw=operator new(sizeof(A));

      A* a=new(raw)A(1)

(当然调用operator new要include “new.h”,因为operator new不是c++原生的操作,定义在一个头文件中)

     其中第一步是分配内存,第二步中的new(raw)部分就要调用operator new( size_t
count
, void * object)了,new(buffer)class(param)这种形式的new也被称为placement new,它的意思是在一个已经存在的内存上构造某个对象,operator new( size_t
count, void * object)这个看似无意义的接口其实是为了这种写法而做的。

   

 

     2.2  delete  ,delete 的完整过程也可分为两步,拿 delete* a举例:

  •      首先调用A的析构函数
  •      然后调用operator delete( void *, void *) ,这个函数其实什么也没干,就是一个空的,它只是对operator new( size_t
    count, void * object)的呼应
  •     调用operator delete( void *),它是对operator new(size_t size)的呼应,默认实现就是一个free(),用于标记内存被回收。

         所以我们可以用下面代码替换delete a:

        a.~A();

        operator delete(raw);

 

    2.3 上面是new和delete的完整过程,响应的还有new[]和delete[]的类似实现,这是分配与释放一个相连的多个实例:

 

        

          void* operator new[](size_t size){
                       void *ptr = (void *)malloc(size);
                      return ptr;
           }

          void operator delete[](void* p){
                   free(p);
                  return;
         }

     

    下面两个是为了placement new[]和delete[]

          void* operator new[](size_t size,void * object){
                                  return object;
           }

          void operator delete[](void* p,void *p){
                  return;
         }

   所以一段传统的代码 A* a=new A[10] 和 delete[] a也可以通过这些operator new[]的调用来实现成如下:

        

          void* raw=operator new(sizeof(A)*10+sizeof(int));
           A* a=new(raw)A[10];

 

          for(size_t i=0;i<10;i++){
                 fl2[i].~leon();
           }
         operator delete[](raw);

      这里在分配内存的地方有个很特殊的地方,我想创建10个A,理应分配sizeof(A)*10大小的内存,但是对于operator new[](size_t size)的调用,必须多给他分配一个sizeof(int),写成上面的void* raw=operator new(sizeof(A)*10+sizeof(int));这是为什么呢?

        原因在于这里是分配连续的N个内存,但是你必须让c++多用一个int来存储这个N,这个int可能是放在这片内存的首位。

         如果不多分配这个空间会怎样?那么operator delete[]时会出错,因为operator delete[]会首先读取一个int知道有多少个A要释放,你没有分配这个int,它可能读到一个不合理的值N,然后去释放,发现释放不了出现内存访问错误,或者N极大一直去释放,表现上程序永远结束不了。

 

   三、重载你的new和delete

      了解了上面的new和delete的过程可以怎样?既然C++已经为我们封装好了new和delete,为什么还要把他们赤裸裸的剥开。答案是:为了重载,为了定义我们自己的new和delete。

       首先一个问题,你可以重载new和delete吗?答案是不能,刚学C++重载时,C++的标准就告诉我们new和delete操作是不能被重载的,但是有的时候我们确实需要定制一些我们自己的new和delete,(最简单的情形是我想统计我一共new了多少内存又delete了多少)。似乎没有解决方法了,但是看到了new和delete的内部,我们就有办法了,因为我们可以重载组成new和delete的各个关键步骤的operator new和operator delete!这是一个令人兴奋的消息。

         为了重载我们要了解operator new 和delete各有几种形式,查查msdn,吓了一跳,包括[]形式一共有3*2*2=12种定义好的重载接口:

  operator new:

      

A形式   这是new的第一步,分配内存
void *__cdecl operator new(
   size_t count
);
B形式  这是for placement new

void *__cdecl operator new(
   size_t count, 
   void * object
) throw();
C形式 这是不抛出异常的形式
void *__cdecl operator new(
   size_t count, 
   const std::nothrow_t&
) throw();

 

 

operator delete:

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

 

 

operator new[]:

 

void *operator new[](
   std::size_t _Count
)
   throw(std::bad_alloc);
void *operator new[](
   std::size_t _Count,
   const std::nothrow_t&
) throw( );
void *operator new[](
   std::size_t _Count, 
   void* _Ptr
) throw( );

 

 

operator delete[]:

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

 

 

C++的运算符重载分为全局重载和局部的类内的重载。

 

  3.1  首先的全局重载,全局重载意味着代码内所有的new和delete的行为都被改变(包括你引用的库),要十分小心,甚至很多人说不要使用全局重载new和delete。

    下面是我全局重载operator new和new[]的第一种A形式(C形式雷同),注意在以上这些操作中,在VS中A和C种方法都可以被用户全局重载,但是B种形式(也就是placement 形式)不允许被重载(它实现成了inline函数),不知道其他IDE是怎样的  规定,但是VS的规定是有道理的,因为我们几乎想不出有什么理由要placement new,它本就是一种为形式而生的形式。。。

  

//overload global operator  new
void* operator new(size_t size){
 cout<<"new "<<size<<endl;
 void *ptr = (void *)malloc(size);
 return ptr;
}

//overload global operator delete
void operator delete(void* p){
 cout<<"delete "<<_msize(p)<<endl;
 free(p);
 return;
}

//overload global operator  new[]
void* operator new[](size_t size){
 cout<<"new[] "<<size<<endl;
 void *ptr = (void *)malloc(size);
 return ptr;
}

//overload global operator  new[]
void operator delete[](void* p){
 cout<<"delete[] "<<_msize(p)<<endl;
 free(p);
 return;
}

 

在我的重载中对于每次施放和分配都额外打印当前涉及内存的大小

 

3.2 类内重载,这种重载更加常用一些,这里要注意,对于类内重载,如果要调用placement  new,那么B形式必须被重载,否则你的代码编不过,这与全局重载完全不同,以下是我对类内实现的A和B形式的重载:

 

   

class leon{
public:
 leon(){
  m_a=0;
 }
 leon(int a){
  m_a=a;

 }
 ~leon(){

 }
 
 //overload operator  new
 void* operator new(size_t size){
  cout<<"new "<<size<<endl;
  void *ptr = (void *)malloc(size);
  return ptr;
 }

 

 void* operator new(size_t size,void* buf){
  cout<<"new "<<size<<endl;
  
  return buf;
 }

 //overload  operator delete
 void operator delete(void* p){
  cout<<"delete "<<_msize(p)<<endl;
  free(p);
  return;
 }

 //overload global operator  new[]
 void* operator new[](size_t size){
  cout<<"new[] "<<size<<endl;
  void *ptr = (void *)malloc(size);
  return ptr;
 }
 void* operator new[](size_t size,void* buf){
  cout<<"new[] "<<size<<endl;
  
  return buf;
 }

 //overload operator  new[]
 void operator delete[](void* p){
  cout<<"delete[] "<<_msize(p)<<endl;
  free(p);
  return;
 }

 

3.3 更多形式的重载:

  其实除了c++定义好的A、B、C三种operator new()接口,我们还可以自定义新的重载形似,如定义一个void* operator new(size_t size,bool b),那么我就可以调用A* a=operator(sizeof(A),true)来执行我的一些特殊需求

 

 四、operator new和delete及其重载的应用:

    对new和delete重载可以在很多地方应用到:

    最常见的就是一些内存管理和统计功能:你想知道你分配和释放了多少内存

    内存泄露的监测:通过统计可以知道是否有内存泄露,甚至定位到于某处有泄露

     另外通过使用placement new还可以在某些场合加快对象的创建,比如你可以事先分配好一片内存,然后利用placement new在上面直接创建,这样之后的每次创建不会再有反复的内存分配和释放操作

    利用上面的特性可以实现某种内存池来加速内存访问

    以上只是抛砖引玉,以后可以再实践中多利用C++的这个特性

   

 

 

  

   

 

 

   

 

 

 

原文链接: https://www.cnblogs.com/weizhixiao/p/5697306.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    Something More about "new" &"delete" in C++

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/98632

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月10日 上午4:51
下一篇 2023年2月10日 上午4:51

相关推荐