深入C++的new
new操作符(new operator) 和 new操作(operator new)的區別。
當你寫這樣的代碼: string *ps = new string("Memory Management");?
 你使用的 new 是?new 操作符。這個操作符就象 sizeof 一樣是語言內置的,你不能改變它的含義,它的功能總是一樣的。它要完成的功能分成兩部分。第一部分是分配足夠的內存以便
 容納所需類型的對象。第二部分是它調用構造函數初始化內存中的對象。new操作符總是做這兩件事情,你不能以任何方式改變它的行為。
?
你所能改變的是如何為對象分配內存。new 操作符調用一個函數來完成必需的內存分配,你能夠重寫或重載這個函數來改變它的行為。new 操作符為分配內存所調用函數的名字是 operator new
?
函數 operator new 通常這樣聲明:?
 void * operator new(size_t size); //參數 size_t確定分配多少內存
 返回值類型是 void*,因為這個函數返回一個未經處理(raw)的指針,未初始化的內存
你一般不會直接調用 operator new,但是一旦這么做,你可以象調用其它函數一樣調用它:?
 void *rawMemory = operator new(sizeof(string));
操作符operator new將返回一個指針, 指向一塊足夠容納一個string類型對象的內存。?
 就象 malloc 一樣,operator new 的職責只是分配內存。它對構造函數一無所知。operator new所了解的是內存分配
void *memory = // 得到未經處理的內存
 operator new(sizeof(string)); // 為 String對象?
 call string::string("Memory Management") //初始化?
 on *memory; // 內存中 // 的對象?
 string *ps = // 是 ps指針指向?
 static_cast<string*>(memory); // 新的對象
 導讀:?
   返回值類型是void*,表示其返回的是一個未經處理(raw)的指針,指向未初始化的內存。參數size_t確定分配多少內存。你能增加額外的參數重載函數operator new,但是第一個參數類型必須是size_t。頭文件中有一個很好的重載的例子,那就是placement new,它看上去象這樣:?
   void * operator new(size_t, void *location)?
   {?
   return location;?
   }?
   這初看上去有些陌生,但它卻是new操作符的一種常見重載方法,使用一個額外的變量buffer,當new操作符隱含調用operator new函數時,把這個變量傳遞給它。被調用的operator new函數除了持有強制的參數size_t外,還必須接受void*指針參數,指向構造對象占用的內存空間。未被使用的(但是強制的)參數size_t沒有參數名字,以防止編譯器警告說它未被使用。在使用placement new的情況下,調用者已經獲得了指向內存的指針,因為調用者知道對象應該放在哪里。placement new需要做的就是返回傳遞給它的指針。?
   我們更經常使用的new是new操作符(new operator),而非操作符new(operator new),如當你使用new操作符構建一個對象的時候,實際上做了兩件事情,一是調用operator new函數獲取內存,二是調用對象的構造函數,如:?
   string *ps = new string("Hello, world!");?
   它完成與下面代碼相似的功能:?
   void *memory = operator new(sizeof(string)); // 為String對象得到未經處理的內存?
   call string::string("Hello, world!") on *memory; // 調用構造函數初始化內存中的對象?
   string *ps = static_cast(memory); // ps指針指向新的對象?
   注意第二步中構造函數的調用只能由編譯器完成,用戶是不允許這樣操作的,也就是說如果你想建立一個堆對象就必須用new操作符,不能直接像上面一樣調用構造函數來初始化堆對象。?
   new操作符(new operator)是編譯器內置的,其行為被語言固定下來,不受用戶控制。但是它們所調用的內存分配函數也就是操作符new(operator new)則可以根據需要進行重載。試著回顧new操作符(new operator)與操作符new(operator new)的關系,如果你想在堆上建立一個對象,應該用new操作符。它既分配內存又為對象調用構造函數。如果你僅僅想分配內存,就應該調用operator new函數,它不會調用構造函數。如果你想定制自己獨有的內存分配過程,你應該重載全局的operator new函數,然后使用new操作符,new操作符會調用你定制的operator new。如果你想在一塊已經獲得指針的內存里建立一個對象,應該使用placement new。?
   最后需要記住的一點是,delete和new一樣具有以上的特性,只是需要注意的一點是delte操作符中是首先調用對象的析構函數,然后再調用operator delete函數的。?
   2. 針對數組的new[]和delete[]操作?
   建立數組時new操作符(new[])的行為與單個對象建立(new)有少許不同:?
   第一是內存不再調用用operator new函數進行分配,代替以operator new[]函數(常稱作array new)。它與operator new一樣能被重載,允許定制數組的內存分配,就象定制單個對象內存分配一樣。?
   第二個不同是new[]操作時調用構造函數的數量。對于new[]而言,在數組里的每一個對象的構造函數都必須被調用。?
   delete[]操作符的語義基本上和new[]相同,他們的實現類似這樣:?
   void * operator new[](size_t size)?
   {?
   cout <<"new size of array in new[](): "<  int *g =(int *) malloc(sizeof(size));?
   return g;?
   }?
   void operator delete[](void* p)?
   {?
   cout <<"delete address of array pointer in delete[](): "<
   free(p);?
   }?
   3. operator new和delete函數的實現?
   operator new實際上總是以標準的C malloc()完成,雖然并沒有規定非得這么做不可。同樣,operator delete也總是以標準得C free()來實現,不考慮異常處理的話他們類似下面的樣子:?
   extern void* operator new( size_t size )?
   {?
   if( size == 0 )?
   size = 1; // 這里保證像 new T[0] 這樣得語句也是可行的?
   ?
   void *last_alloc;?
   while( !(last_alloc = malloc( size )) )?
   {?
   if( _new_handler )?
   ( *_new_handler )();?
   else?
   return 0;?
   }?
   return last_alloc;?
   }?
   extern void operator delete( void *ptr )?
   {?
   if(ptr) // 從這里可以看出,刪除一個空指針是安全的?
   free( (char*)ptr );?
   }
==============================================================================================================================
先舉個例子:
?“operator new”:
[cpp]?view plaincopy
注意參數,operator new 的形參類型是 size_t, 此函數用來分配內存,約等于 malloc,返回的是 void*,并不是 Foo*。確實與“new operator” 不同。
 "operator new" 其實是函數。有全局作用域版,也可以在類內覆寫自己的成員函數版。如果覆寫了成員函數版,那么在 new something 的時候全局版就會被隱藏。
 
 
 
 "new operator" :
 
[cpp]?view plaincopy
“new operator” ,有人說其實根本沒有這個術語。 c++ 標準里面 new/delete 雖然跟 sizeof之類的同為關鍵字,并且使用方式也相似,但是new/delete 不能叫做“operator”,而是叫做“expression”,參考討論(http://stackoverflow.com/questions/1885849),,不過誰知道呢,也有人說這個是c++標準里面闡述不明確的地方之一。據說 More Effective C++ 用過 “new operator” 這一說法 :
 The new operator calls a function to perform the requisite memory allocation, and you can rewrite or overload that function to change its behavior. The name of the function the new operator calls to allocate memory is operator new.
 
 并且msdn也用了 “new operator” :http://msdn.microsoft.com/en-us/library/kftdy56f%28VS.71%29.aspx
 
 所以管他呢。。就當是有這么個術語啦。。
 
上面 Foo* foo=new Foo(); 這一句執行的時候,實際上先后調用了 “operator new” 和 constructor 兩個函數。 operator new 分配了 sizeof(Foo) 大小的內存,然后 constructor 做初始化。因此輸出了:
in Foo's operator new
 in Foo's ctor ?
 
================================================================================================================
“new”是C++的一個關鍵字,同時也是操作符。關于new的話題非常多,因為它確實比較復雜,也非常神秘,下面我將把我了解到的與new有關的內容做一個總結。
new的過程 當我們使用關鍵字new在堆上動態創建一個對象時,它實際上做了三件事:獲得一塊內存空間、調用構造函數、返回正確的指針。當然,如果我們創建的是簡單類型的變量,那么第二步會被省略。假如我們定義了如下一個類A: class A{
?? int i;
public:
?? A(int _i) :i(_i*_i) {}
?? void Say() ?{ printf("i=%d/n", i); }
};
//調用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”,但事實上在C++中一提到new,至少可能代表以下三種含義:new operator、operator new、placement new。 new operator就是我們平時所使用的new,其行為就是前面所說的三個步驟,我們不能更改它。但具體到某一步驟中的行為,如果它不滿足我們的具體要求時,我們是有可能更改它的。三個步驟中最后一步只是簡單的做一個指針的類型轉換,沒什么可說的,并且在編譯出的代碼中也并不需要這種轉換,只是人為的認識罷了。但前兩步就有些內容了。 new operator的第一步分配內存實際上是通過調用operator new來完成的,這里的new實際上是像加減乘除一樣的操作符,因此也是可以重載的。operator new默認情況下首先調用分配內存的代碼,嘗試得到一段堆上的空間,如果成功就返回,如果失敗,則轉而去調用一個new_hander,然后繼續重復前面過程。如果我們對這個過程不滿意,就可以重載operator new,來設置我們希望的行為。例如: class A
{
public:
?? void* operator new(size_t size)
?? {
?????? printf("operator new called/n");
?????? return ::operator new(size);
?? }
};
A* a = new A(); 這里通過::operator new調用了原有的全局的new,實現了在分配內存之前輸出一句話。全局的operator new也是可以重載的,但這樣一來就不能再遞歸的使用new來分配內存,而只能使用malloc了: void* operator new(size_t size)
{
?? printf("global new/n");
???return malloc(size);
} 相應的,delete也有delete operator和operator delete之分,后者也是可以重載的。并且,如果重載了operator new,就應該也相應的重載operator delete,這是良好的編程習慣。 new的第三種形態——placement new是用來實現定位構造的,因此可以實現new operator三步操作中的第二步,也就是在取得了一塊可以容納指定類型對象的內存后,在這塊內存上構造一個對象,這有點類似于前面代碼中的“p->A::A(3);”這句話,但這并不是一個標準的寫法,正確的寫法是使用placement new: #include <new.h>
void main()
{
?? char s[sizeof(A)];
?? A* p = (A*)s;
?? new(p) A(3); //p->A::A(3);
?? p->Say();
} 對頭文件<new>或<new.h>的引用是必須的,這樣才可以使用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)
{
?? void* p = null
?? while(!(p = malloc(size)))
?? {
?????? if(null == new_handler)
????????? throw bad_alloc();
?????? try
?????? {
????????? new_handler();
?????? }
?????? catch(bad_alloc e)
?????? {
????????? throw e;
?????? }
?????? catch(…)
?????? {}
?? }
?? return p;
} 在默認情況下,new_handler的行為是拋出一個bad_alloc異常,因此上述循環只會執行一次。但如果我們不希望使用默認行為,可以自定義一個new_handler,并使用std::set_new_handler函數使其生效。在自定義的new_handler中,我們可以拋出異常,可以結束程序,也可以運行一些代碼使得有可能有內存被空閑出來,從而下一次分配時也許會成功,也可以通過set_new_handler來安裝另一個可能更有效的new_handler。例如: void MyNewHandler()
{
?? printf(“New handler called!/n”);
?? throw std::bad_alloc();
}
std::set_new_handler(MyNewHandler); 這里new_handler程序在拋出異常之前會輸出一句話。應該注意,在new_handler的代碼里應該注意避免再嵌套有對new的調用,因為如果這里調用new再失敗的話,可能會再導致對new_handler的調用,從而導致無限遞歸調用。——這是我猜的,并沒有嘗試過。 在編程時我們應該注意到對new的調用是有可能有異常被拋出的,因此在new的代碼周圍應該注意保持其事務性,即不能因為調用new失敗拋出異常來導致不正確的程序邏輯或數據結構的出現。例如: class SomeClass
{
?? static int count;
?? SomeClass() {}
public:
?? static SomeClass* GetNewInstance()
?? {
?????? count++;
?????? return new SomeClass();
?? }
}; 靜態變量count用于記錄此類型生成的實例的個數,在上述代碼中,如果因new分配內存失敗而拋出異常,那么其實例個數并沒有增加,但count變量的值卻已經多了一個,從而數據結構被破壞。正確的寫法是: static SomeClass* GetNewInstance()
{
?? SomeClass* p = new SomeClass();
?? count++;
?? return p;
} 這樣一來,如果new失敗則直接拋出異常,count的值不會增加。類似的,在處理線程同步時,也要注意類似的問題: void SomeFunc()
{
?? lock(someMutex); //加一個鎖
?? delete p;
?? p = new SomeClass();
?? unlock(someMutex);
} 此時,如果new失敗,unlock將不會被執行,于是不僅造成了一個指向不正確地址的指針p的存在,還將導致someMutex永遠不會被解鎖。這種情況是要注意避免的。(參考:C++箴言:爭取異常安全的代碼) STL的內存分配與traits技巧 在《STL原碼剖析》一書中詳細分析了SGI STL的內存分配器的行為。與直接使用new operator不同的是,SGI STL并不依賴C++默認的內存分配方式,而是使用一套自行實現的方案。首先SGI STL將可用內存整塊的分配,使之成為當前進程可用的內存,當程序中確實需要分配內存時,先從這些已請求好的大內存塊中嘗試取得內存,如果失敗的話再嘗試整塊的分配大內存。這種做法有效的避免了大量內存碎片的出現,提高了內存管理效率。 為了實現這種方式,STL使用了placement new,通過在自己管理的內存空間上使用placement new來構造對象,以達到原有new operator所具有的功能。 template <class T1, class T2>
inline void construct(T1* p, const T2& value)
{
?? new(p) T1(value);
} 此函數接收一個已構造的對象,通過拷貝構造的方式在給定的內存地址p上構造一個新對象,代碼中后半截T1(value)便是placement new語法中調用構造函數的寫法,如果傳入的對象value正是所要求的類型T1,那么這里就相當于調用拷貝構造函數。類似的,因使用了placement new,編譯器不會自動產生調用析構函數的代碼,需要手工的實現: template <class T>
inline void destory(T* pointer)
{
?? pointer->~T();
} 與此同時,STL中還有一個接收兩個迭代器的destory版本,可將某容器上指定范圍內的對象全部銷毀。典型的實現方式就是通過一個循環來對此范圍內的對象逐一調用析構函數。如果所傳入的對象是非簡單類型,這樣做是必要的,但如果傳入的是簡單類型,或者根本沒有必要調用析構函數的自定義類型(例如只包含數個int成員的結構體),那么再逐一調用析構函數是沒有必要的,也浪費了時間。為此,STL使用了一種稱為“type traits”的技巧,在編譯器就判斷出所傳入的類型是否需要調用析構函數: template <class ForwardIterator>
inline void destory(ForwardIterator first, ForwardIterator last)
{
?? __destory(first, last, value_type(first));
} 其中value_type()用于取出迭代器所指向的對象的類型信息,于是: template<class ForwardIterator, class T>
inline void __destory(ForwardIterator first, ForwardIterator last, T*)
{
?? typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
?? __destory_aux(first, last, trivial_destructor());
}
//如果需要調用析構函數:
template<class ForwardIterator>
inline void __destory_aux(ForwardIterator first, ForwardIterator last, __false_type)
{
?? for(; first < last; ++first)
?????? destory(&*first); //因first是迭代器,*first取出其真正內容,然后再用&取地址
}
//如果不需要,就什么也不做:
tempalte<class ForwardIterator>
inline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type)
{} 因上述函數全都是inline的,所以多層的函數調用并不會對性能造成影響,最終編譯的結果根據具體的類型就只是一個for循環或者什么都沒有。這里的關鍵在于__type_traits<T>這個模板類上,它根據不同的T類型定義出不同的has_trivial_destructor的結果,如果T是簡單類型,就定義為__true_type類型,否則就定義為__false_type類型。其中__true_type、__false_type只不過是兩個沒有任何內容的類,對程序的執行結果沒有什么意義,但在編譯器看來它對模板如何特化就具有非常重要的指導意義了,正如上面代碼所示的那樣。__type_traits<T>也是特化了的一系列模板類: struct __true_type {};
struct __false_type {};
template <class T>
struct __type_traits
{
public:
?? typedef __false _type has_trivial_destructor;
???……
};
template<>?//模板特化
struct __type_traits<int>??? //int的特化版本
{
public:
?? typedef __true_type has_trivial_destructor;
???……
};
…… //其他簡單類型的特化版本 如果要把一個自定義的類型MyClass也定義為不調用析構函數,只需要相應的定義__type_traits<T>的一個特化版本即可: template<>
struct __type_traits<MyClass>
{
public:
?? typedef __true_type has_trivial_destructor;
???……
}; 模板是比較高級的C++編程技巧,模板特化、模板偏特化就更是技巧性很強的東西,STL中的type_traits充分借助模板特化的功能,實現了在程序編譯期通過編譯器來決定為每一處調用使用哪個特化版本,于是在不增加編程復雜性的前提下大大提高了程序的運行效率。更詳細的內容可參考《STL源碼剖析》第二、三章中的相關內容。 帶有“[]”的new和delete 我們經常會通過new來動態創建一個數組,例如: char* s = new char[100];
……
delete s; 嚴格的說,上述代碼是不正確的,因為我們在分配內存時使用的是new[],而并不是簡單的new,但釋放內存時卻用的是delete。正確的寫法是使用delete[]: delete[] s; 但是,上述錯誤的代碼似乎也能編譯執行,并不會帶來什么錯誤。事實上,new與new[]、delete與delete[]是有區別的,特別是當用來操作復雜類型時。假如針對一個我們自定義的類MyClass使用new[]: MyClass* p = new MyClass[10]; 上述代碼的結果是在堆上分配了10個連續的MyClass實例,并且已經對它們依次調用了構造函數,于是我們得到了10個可用的對象,這一點與Java、C#有區別的,Java、C#中這樣的結果只是得到了10個null。換句話說,使用這種寫法時MyClass必須擁有不帶參數的構造函數,否則會發現編譯期錯誤,因為編譯器無法調用有參數的構造函數。 當這樣構造成功后,我們可以再將其釋放,釋放時使用delete[]: delete[] p; 當我們對動態分配的數組調用delete[]時,其行為根據所申請的變量類型會有所不同。如果p指向簡單類型,如int、char等,其結果只不過是這塊內存被回收,此時使用delete[]與delete沒有區別,但如果p指向的是復雜類型,delete[]會針對動態分配得到的每個對象調用析構函數,然后再釋放內存。因此,如果我們對上述分配得到的p指針直接使用delete來回收,雖然編譯期不報什么錯誤(因為編譯器根本看不出來這個指針p是如何分配的),但在運行時(DEBUG情況下)會給出一個Debug assertion failed提示。 到這里,我們很容易提出一個問題——delete[]是如何知道要為多少個對象調用析構函數的?要回答這個問題,我們可以首先看一看new[]的重載。 class MyClass
{
???int a;
public:
?? MyClass() { printf("ctor/n"); }
?? ~MyClass() { printf("dtor/n"); }
};
void* operator new[](size_t size)
{
???void* p = operator new(size);
?? printf("calling new[] with size=%d address=%p/n", size, p);
???return p;
}
// 主函數
MyClass* mc = new MyClass[3];
printf("address of mc=%p/n", mc);
delete[] mc; 運行此段代碼,得到的結果為:(VC2005) calling new[] with size=16?address=003A5A58 ctor ctor ctor address of mc=003A5A5C dtor dtor dtor 雖然對構造函數和析構函數的調用結果都在預料之中,但所申請的內存空間大小以及地址的數值卻出現了問題。我們的類MyClass的大小顯然是4個字節,并且申請的數組中有3個元素,那么應該一共申請12個字節才對,但事實上系統卻為我們申請了16字節,并且在operator new[]返后我們得到的內存地址是實際申請得到的內存地址值加4的結果。也就是說,當為復雜類型動態分配數組時,系統自動在最終得到的內存地址前空出了4個字節,我們有理由相信這4個字節的內容與動態分配數組的長度有關。通過單步跟蹤,很容易發現這4個字節對應的int值為0x00000003,也就是說記錄的是我們分配的對象的個數。改變一下分配的個數然后再次觀察的結果證實了我的想法。于是,我們也有理由認為new[] operator的行為相當于下面的偽代碼: template <class T>
T* New[](int count)
{
?? int size = sizeof(T) * count + 4;
?? void* p = T::operator new[](size);
?? *(int*)p = count;
?? T* pt = (T*)((int)p + 4);
?? for(int i = 0; i < count; i++)
?????? new(&pt[i]) T();
?? return pt;
} 上述示意性的代碼省略了異常處理的部分,只是展示當我們對一個復雜類型使用new[]來動態分配數組時其真正的行為是什么,從中可以看到它分配了比預期多4個字節的內存并用它來保存對象的個數,然后對于后面每一塊空間使用placement new來調用無參構造函數,這也就解釋了為什么這種情況下類必須有無參構造函數,最后再將首地址返回。類似的,我們很容易寫出相應的delete[]的實現代碼: template <class T>
void Delete[](T* pt)
{
?? int count = ((int*)pt)[-1];
?? for(int i = 0; i < count; i++)
?????? pt[i].~T();
?? void* p = (void*)((int)pt – 4);
?? T::operator delete[](p);
} 由此可見,在默認情況下operator new[]與operator new的行為是相同的,operator delete[]與operator delete也是,不同的是new operator與new[] operator、delete operator與delete[] operator。當然,我們可以根據不同的需要來選擇重載帶有和不帶有“[]”的operator new和delete,以滿足不同的具體需求。 把前面類MyClass的代碼稍做修改——注釋掉析構函數,然后再來看看程序的輸出: calling new[] with size=12 address=003A5A58 ctor ctor ctor address of mc=003A5A58 這一次,new[]老老實實的申請了12個字節的內存,并且申請的結果與new[] operator返回的結果也是相同的,看來,是否在前面添加4個字節,只取決于這個類有沒有析構函數,當然,這么說并不確切,正確的說法是這個類是否需要調用構造函數,因為如下兩種情況下雖然這個類沒聲明析構函數,但還是多申請了4個字節:一是這個類中擁有需要調用析構函數的成員,二是這個類繼承自需要調用析構函數的類。于是,我們可以遞歸的定義“需要調用析構函數的類”為以下三種情況之一: 1 顯式的聲明了析構函數的 2 擁有需要調用析構函數的類的成員的 3 繼承自需要調用析構函數的類的 類似的,動態申請簡單類型的數組時,也不會多申請4個字節。于是在這兩種情況下,釋放內存時使用delete或delete[]都可以,但為養成良好的習慣,我們還是應該注意只要是動態分配的數組,釋放時就使用delete[]。 釋放內存時如何知道長度 但這同時又帶來了新問題,既然申請無需調用析構函數的類或簡單類型的數組時并沒有記錄個數信息,那么operator delete,或更直接的說free()是如何來回收這塊內存的呢?這就要研究malloc()返回的內存的結構了。與new[]類似的是,實際上在malloc()申請內存時也多申請了數個字節的內容,只不過這與所申請的變量的類型沒有任何關系,我們從調用malloc時所傳入的參數也可以理解這一點——它只接收了要申請的內存的長度,并不關系這塊內存用來保存什么類型。下面運行這樣一段代碼做個實驗: char *p = 0;
for(int i = 0; i < 40; i += 4)
{
???char* s = new char[i];
?? printf("alloc %2d bytes, address=%p distance=%d/n", i, s, s - p);
?? p = s;
} 我們直接來看VC2005下Release版本的運行結果,DEBUG版因包含了較多的調試信息,這里就不分析了: alloc?0 bytes, address=003A36F0 distance=3815152 alloc?4 bytes, address=003A3700 distance=16 alloc?8 bytes, address=003A3710 distance=16 alloc 12 bytes, address=003A3720 distance=16 alloc 16 bytes, address=003A3738 distance=24 alloc 20 bytes, address=003A84C0 distance=19848 alloc 24 bytes, address=003A84E0 distance=32 alloc 28 bytes, address=003A8500 distance=32 alloc 32 bytes, address=003A8528 distance=40 alloc 36 bytes, address=003A8550 distance=40 每一次分配的字節數都比上一次多4,distance值記錄著與上一次分配的差值,第一個差值沒有實際意義,中間有一個較大的差值,可能是這塊內存已經被分配了,于是也忽略它。結果中最小的差值為16字節,直到我們申請16字節時,這個差值變成了24,后面也有類似的規律,那么我們可以認為申請所得的內存結構是如下這樣的: 從圖中不難看出,當我們要分配一段內存時,所得的內存地址和上一次的尾地址至少要相距8個字節(在DEBUG版中還要更多),那么我們可以猜想,這8個字節中應該記錄著與這段所分配的內存有關的信息。觀察這8個節內的內容,得到結果如下: 圖中右邊為每次分配所得的地址之前8個字節的內容的16進制表示,從圖中紅線所表示可以看到,這8個字節中的第一個字節乘以8即得到相臨兩次分配時的距離,經過試驗一次性分配更大的長度可知,第二個字節也是這個意義,并且代表高8位,也就說前面空的這8個字節中的前兩個字節記錄了一次分配內存的長度信息,后面的六個字節可能與空閑內存鏈表的信息有關,在翻譯內存時用來提供必要的信息。這就解答了前面提出的問題,原來C/C++在分配內存時已經記錄了足夠充分的信息用于回收內存,只不過我們平常不關心它罷了。 ====================================================================================================== 1. plain new 普通new
[cpp]?view plaincopy
[cpp]?view plaincopy
[cpp]?view plaincopy
該函數的第2形參是 struct nothrow_t { ?};它是個全局常對象 const nothrow_t nothrow; 用來作為 new 運算符的標志,以區別前一個new.
3.placement new 放置new
[cpp]?view plaincopy
[cpp]?view plaincopy
該運算符的作用是:只要第一次分配成功,不再擔心分配失敗。
[cpp]?view plaincopy
? ? 特別要注意的是對于 placement new 絕不可以調用的delete, 因為該new只是使用別人替它申請的地方(只是個租房戶,不是房主。無權將房子賣掉)。釋放內存是nothrow new的事,即要使用原來的指針釋放內存
總結
                            
                        - 上一篇: Valgrind简单用法
 - 下一篇: operator new在C++中的各种