閱讀969 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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++的這個特性

   

 

 

  

   

 

 

   

 

 

 

最後更新:2017-04-03 16:48:51

  上一篇:go Linux網絡設置3——ssh工具使用的注意點
  下一篇:go 最近在搞264壓縮,不知道難度有多大?