new的深入分析

2021-05-22 04:47:53 字數 3632 閱讀 3285

「new」是c++的乙個關鍵字,同時也是操作符。關於new的話題非常多,因為它確實比較複雜,也非常神秘,下面我將把我了解到的與new有關的內容做乙個總結。

new的過程

當我們使用關鍵字new在堆上動態建立乙個物件時,它實際上做了三件事:獲得一塊記憶體空間、呼叫建構函式、返回正確的指標。當然,如果我們建立的是簡單型別的變數,那麼第二步會被省略。假如我們定義了如下乙個類a:

class a

void say()

};//呼叫new:

a* pa = new a(3);

那麼上述動態建立乙個物件的過程大致相當於以下三句話(只是大致上):

a* pa = (a*)malloc(sizeof(a));

pa->a::a(3);

return pa;

雖然從效果上看,這三句話也得到了乙個有效的指向堆上的a物件的指標pa,但區別在於,當malloc失敗時,它不會呼叫分配記憶體失敗處理程式new_handler,而使用new的話會的。因此我們還是要盡可能的使用new,除非有一些特殊的需求。

new的三種形態

到目前為止,本文所提到的new都是指的「new operator」或稱為「new expression_r_r」,但事實上在c++中一提到new,至少可能代表以下三種含義:new operator、operator new、placement new

new operator就是我們平時所使用的new,其行為就是前面所說的三個步驟,我們不能更改它。但具體到某一步驟中的行為,如果它不滿足我們的具體要求時,我們是有可能更改它的。三個步驟中最後一步只是簡單的做乙個指標的型別轉換,沒什麼可說的,並且在編譯出的**中也並不需要這種轉換,只是人為的認識罷了。但前兩步就有些內容了。

new operator的第一步分配記憶體實際上是通過呼叫operator new來完成的,這裡的new實際上是像加減乘除一樣的操作符,因此也是可以過載的。operator new預設情況下首先呼叫分配記憶體的**,嘗試得到一段堆上的空間,如果成功就返回,如果失敗,則轉而去呼叫乙個new_hander,然後繼續重複前面過程。如果我們對這個過程不滿意,就可以過載operator new,來設定我們希望的行為。例如:

class a

};a* a = new a();

這裡通過::operator new呼叫了原有的全域性的new,實現了在分配記憶體之前輸出一句話。全域性的operator new也是可以過載的,但這樣一來就不能再遞迴的使用new來分配記憶體,而只能使用malloc了:

void* operator new(size_t size)

相應的,delete也有delete operator和operator delete之分,後者也是可以過載的。並且,如果過載了operator new,就應該也相應的過載operator delete,這是良好的程式設計習慣

所以我們平時過載new時只需malloc就可以,不用呼叫類的建構函式。

new的第三種形態——placement new是用來實現定位構造的,因此可以實現new operator三步操作中的第二步,也就是在取得了一塊可以容納指定型別物件的記憶體後,在這塊記憶體上構造乙個物件,這有點類似於前面**中的 「p->a::a(3);」這句話,但這並不是乙個標準的寫法,正確的寫法是使用placement new:

#include

void main()

對頭檔案或的引用是必須的,這樣才可以使用placement new。這裡「new(p) a(3)」這種奇怪的寫法便是placement new了,它實現了在指定記憶體位址上用指定型別的建構函式來構造乙個物件的功能,後面a(3)就是對建構函式的顯式呼叫。這裡不難發現,這塊指定的位址既可以是棧,又可以是堆,placement對此不加區分。但是,除非特別必要,不要直接使用placement new ,這畢竟不是用來構造物件的正式寫法,只不過是new operator的乙個步驟而已。使用new operator地編譯器會自動生成對placement new的呼叫的**,因此也會相應的生成使用delete時呼叫析構函式的**。如果是像上面那樣在棧上使用了placement new,則必須手工呼叫析構函式,這也是顯式呼叫析構函式的唯一情況:

p->~a();

當我們覺得預設的new operator對記憶體的管理不能滿足我們的需要,而希望自己手工的管理記憶體時,placement new就有用了。stl中的allocator就使用了這種方式,借助placement new來實現更靈活有效的記憶體管理。

處理記憶體分配異常

正如前面所說,operator new的預設行為是請求分配記憶體,如果成功則返回此記憶體位址,如果失敗則呼叫乙個new_handler,然後再重複此過程。於是,想要從operator new的執行過程中返回,則必然需要滿足下列條件之一:

l 分配記憶體成功

l new_handler中丟擲bad_alloc異常

l new_handler中呼叫exit()或類似的函式,使程式結束

於是,我們可以假設預設情況下operator new的行為是這樣的:

void* operator new(size_t size)

catch(bad_alloc e)

catch(…){}}

return p;

}在預設情況下,new_handler的行為是丟擲乙個bad_alloc異常,因此上述迴圈只會執行一次。但如果我們不希望使用預設行為,可以自定義乙個new_handler,並使用std::set_new_handler函式使其生效。在自定義的new_handler中,我們可以丟擲異常,可以結束程式,也可以執行一些**使得有可能有記憶體被空閒出來,從而下一次分配時也許會成功,也可以通過set_new_handler來安裝另乙個可能更有效的new_handler。例如:

void mynewhandler()

std::set_new_handler(mynewhandler);

這裡new_handler程式在丟擲異常之前會輸出一句話。應該注意,在 new_handler的**裡應該注意避免再巢狀有對new的呼叫,因為如果這裡呼叫new再失敗的話,可能會再導致對new_handler的呼叫,從而導致無限遞迴呼叫。——這是我猜的,並沒有嘗試過。

在程式設計時我們應該注意到對new的呼叫是有可能有異常被丟擲的,因此在new的**周圍應該注意保持其事務性,即不能因為呼叫new失敗丟擲異常來導致不正確的程式邏輯或資料結構的出現。例如:

class someclass

public:

static someclass* getnewinstance()

};靜態變數count用於記錄此型別生成的例項的個數,在上述**中,如果因new分配記憶體失敗而丟擲異常,那麼其實例個數並沒有增加,但count變數的值卻已經多了乙個,從而資料結構被破壞。正確的寫法是:

static someclass* getnewinstance()

這樣一來,如果new失敗則直接丟擲異常,count的值不會增加。類似的,在處理執行緒同步時,也要注意類似的問題:

void somefunc()

此時,如果new失敗,unlock將不會被執行,於是不僅造成了乙個指向不正確位址的指標p的存在,還將導致somemutex永遠不會被解鎖。這種情況是要注意避免的。

malloc和new的深入分析

1.malloc 函式 1.1 malloc的全稱是memory allocation,中文叫動態記憶體分配。原型 extern void malloc unsigned int num bytes 說明 分配長度為num bytes位元組的記憶體塊。如果分配成功則返回指向被分配記憶體的指標,分配失...

IsPostBack深入分析

1 ispostback 介紹 ispostback是 page類有乙個 bool型別的屬性,用來判斷針對當前 form的請求是第一次還是非第一次請求。當 ispostback true時表示非第一次請求,我們稱為 postback,當 ispostback false時表示第一次請求。在 asp....

深入分析ConcurrentHashMap

再多執行緒的情況下,如果使用hashmap,就會導致死迴圈,導致cpu利用率接近100 所以如果是併發的情況不要使用hashmap 導致死迴圈主要是這段 當在多執行緒的情況由於沒有同步導致,著段 在擴容的時候會執行 do while e null 執行緒安全的hashtable 容器 hashtab...