“瑜珈山夜話” ----記憶體分配(二) (轉)

amyz發表於2007-11-16
“瑜珈山夜話” ----記憶體分配(二) (轉)[@more@]

  new/delete究竟做了些什麼?
  在理解這個問題之前,我們先看一下下面的這段,有這麼一個程式段:
  class A
  {
  public:
  A() {  cout<  ~A(){  cout<  private:
  int i;
  };
  A* pA=new A;
  delete pA;
  在這個簡單的程式段裡面,new/delete究竟做了些什麼?
  實際上,這段程式裡面隱含了一些我們沒有看到的東西,那就是:
  static void* operator new(size_t sz);
  static void  operator delete(void* p);
  值得注意的是,這兩個都是static的,所以如果我們過載了這2個函式(我們要麼不過載,要過載就要2個一起行動),也應該宣告為static的,如果我們沒有宣告,也會為我們自動加上。另外,這是兩個分配原語,要麼成功,要麼沒有分配任何記憶體。
  size_t是什麼東西呢?我在第一次看到這個動動的時候也是十分的困惑,畢竟以前沒有見過。size_t在中定義,是一種無符號整數型別(不一定是int),用來儲存的大小,這一用法是從C語言中借用過來的,現在你應該明白了吧(我學習的時候可是鬱悶了好幾天,沒有人可以問,因為不知道有個csdn:)
  new A;實際上做了2件事:呼叫opeator new,在自由區分配一個sizeof(A)大小的記憶體空間;然後呼叫建構函式A(),在這塊記憶體空間上類磚砌瓦,建造起我們的物件。同樣對於delete,則做了相反的兩件事:呼叫解構函式~A(),銷燬物件,呼叫operator delete,釋放記憶體。不過需要注意的是,new分配一塊記憶體的時候,並沒有對這塊記憶體空間做清零等任何動作,只是拿了過來,這塊記憶體上放的仍然是原來的資料(垃圾資料),delete的時候,也只是釋放這塊記憶體,歸還給,上面的資料還在上面,所以delete pA之後,pA的值沒變,他指向的那塊記憶體的值也沒有變,不過似乎有什麼問題,我們看一下下面的這個程式段:
 int *p=new int(50000);
 cout< delete p;
 cout<  我們可以清楚地看到,指標p存放的資料仍然是原來的地址,但是*p的內容卻發生了變化,在我的機器上(, VC6)始終是-572662307,不清楚這是為什麼,難道系統做了什麼手腳?還望高手指教。 
  在這裡我們可以看到,new的工作實際上就是保證相互分離的儲存分配和初始化工作能夠很好的在一起工作,不過這裡可能讓初學者迷惑的是,我們定義了一個帶有引數的new,但是我們用的時候卻沒有顯式的去呼叫,而是讓系統“神秘”的去提供這個引數。是的,這樣做毫無疑問增加了複雜性,但是讓基類獲取了為一集派生類提供分配和釋放服務的能力。 

  new/delete有什麼好處和壞處?
  從C程式設計師轉換過來的C++程式設計師總是有個困惑:new/delete到底究竟和C語言裡面的malloc/free比起來有什麼優勢?或者是一樣的?
  其實,就算我不說,你也應該很清楚了,new/delete當然比malloc/free要好,要不然,為什麼還引進這個東東呢?其實透過上面的分析,我們看到了new/delete實際上做了很多malloc/free沒有做的事情:malloc/free只是對記憶體進行分配和釋放;new/delete還負責完成了建立和銷燬物件的任務。
  另外,new的性要高一些,因為他返回的就是一個所建立的物件的指標,對於malloc來說返回的則是void*,還要進行強制型別轉換,顯然這是一個危險的。
  最後,我們可以對new/delete過載,使記憶體分配按照我們的意願進行,這樣更具有靈活性,malloc則不行。
  不過,new/delete也並不是十分完美,大概最大的缺點就是低(針對的是預設的分配器),原因不只是因為在自由儲存區上分配(和棧上對比),具體的原因目前不是很清楚,不過在MCD上說了2個可能的原因:
  1、new只是對於堆分配器(malloc/realloc/free)的一個淺層包裝,沒有針對小型的記憶體分配做。
  2、預設分配器具有通用性,它管理的是一塊記憶體池,這樣的管理往往需要消耗一些額外空間。

  各種各樣的new
  一般來說,new有很多種形式,不過真的歸納起來,也就是2種:
  1、最常用的形式:
  void *operator new(std::size_t sz)  throw(std::bad_alloc);  (普通的)
  void *operator new[](std::size_t sz)  throw(std::bad_alloc);  (陣列的)
  void *operator new(std::size_t sz);
  void *operator new[](std::size_t sz)
  這一種大家用得最為頻繁,我就不舉例子了。
  2、放置new形式:
  void *operator new(std::size_t count, void *ptr)  throw();  (普通的)
  void *operator new[](std::size_t count, void *ptr) throw();  (陣列的)
 要使用這種方式,必須包含頭。這個機制引入的初始目的是為了解決2個相關的問題:
  1、把一個物件放在某個特定位置;
  2、在某個特定分配區裡面分配物件;
  但是引入之後,發現這種機制遠超出了簡單的儲存分配機制,我們可以給特定的儲存位置關聯任意的邏輯性值,這樣一來,new就有了一種通用的作用。同時第二個引數,也被擴充套件成了任意的可以識別的型別,並且配備了相應的nothrow版本:
  void* operator new(std::size_t, const std::nothrow_t&)  throw();
  void* operator new[](std::size_t, const std::nothrow_t&) throw();

  new能夠返回NULL麼?
  我們經常看到有很多初學者喜歡寫如下程式碼:
  A* p=new A();
  if(p==NUL) ....
  寫下這段程式碼的可能是受到了一些書上錯誤的影響,因為new A()從來就不可能返回NULL,如果在這個過程中用完了記憶體,那麼他就會丟擲bad_alloc異常,絕對不會返回NULL,如果你想讓他返回null,應該用new(nothrow) A(),而不是new A()。不過從異常的觀點來看,這實際上是一種倒退,我們應該儘量迴避。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982742/,如需轉載,請註明出處,否則將追究法律責任。

相關文章