【C++】C++ 記憶體分配(new,operator new)詳解

漫步雲端ly發表於2017-12-20

本文主要講述C++ new關鍵字和operator new, placement new之間的種種關聯,new的底層實現,以及operator new的過載和一些在記憶體池,STL中的應用。

一. new operator 和 operator new

  • new operator:指我們在C++裡通常用到的關鍵字,比如A* a = new A;
  • operator new:它是一個操作符,並且可被過載(類似加減乘除的操作符過載)

關於這兩者的關係,我找到一段比較經典的描述(來自於www.cplusplus.com 見參考文獻:

operator new can be called explicitly as a regular function, but in C++, new is an operator with a very specific behavior: An expression with the new operator, first calls function operator new (i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.

比如我們寫如下程式碼:

A* a = new A;

我們知道這裡分為三步:1.分配記憶體,2.呼叫A()構造物件,3. 返回分配指標。事實上,分配記憶體這一操作就是由operator new(size_t)來完成的,如果類A過載了operator new,那麼將呼叫A::operator new(size_t ),否則呼叫全域性::operator new(size_t ),後者由C++預設提供。因此前面的步驟也就是:

  1. 呼叫operator new (sizeof(A))
  2. 呼叫A:A()
  3. 返回指標

這裡再一次提出來是因為後面關於這兩步會有一些變形,在關於placement new那裡會講到。先舉個簡單例子

[cpp] view plain copy
  1. //平臺:Visual Stdio 2008  
  2. #include<iostream>  
  3. class A  
  4. {  
  5. public:  
  6.      A()  
  7.      {  
  8.           std::cout<<"call A constructor"<<std::endl;  
  9.      }  
  10.   
  11.      ~A()  
  12.      {  
  13.           std::cout<<"call A destructor"<<std::endl;  
  14.      }  
  15. }  
  16. int _tmain(int argc, _TCHAR* argv[])  
  17. {  
  18.   
  19.      A* a = new A;  
  20.      delete a;  
  21.   
  22.      system("pause");  
  23.      return 0;  
  24. }  

下面我們跟蹤一下A反彙編程式碼,由於Debug版本反彙編跳轉太多,因此此處通過Release版本在A* a = new A;處設斷點反彙編: 
在Release版本中,建構函式和解構函式都是直接展開的。

[cpp] view plain copy
  1.     A* a = new A;  
  2. 01301022  push        1    ;不含資料成員的類佔用一位元組空間,此處壓入sizeof(A)  
  3. 01301024  call        operator new (13013C2h) ;呼叫operator new(size_t size)  
  4. 01301029  mov         esi,eax ;返回值儲存到esi  
  5. 0130102B  add         esp,4 ;平衡棧  
  6. 0130102E  mov         dword ptr [esp+8],esi ;  
  7. 01301032  mov         dword ptr [esp+14h],0   
  8. 0130103A  test        esi,esi ;在operator new之後,檢查其返回值,如果為空(分配失敗),則不呼叫A()建構函式  
  9. 0130103C  je          wmain+62h (1301062h) ;為空 跳過建構函式部分  
  10. 0130103E  mov         eax,dword ptr [__imp_std::endl (1302038h)] ;建構函式內部,輸出字串  
  11. 01301043  mov         ecx,dword ptr [__imp_std::cout (1302050h)]   
  12. 01301049  push        eax    
  13. 0130104A  push        offset string "call A constructor" (1302134h)   
  14. 0130104F  push        ecx    
  15. 01301050  call        std::operator<<<std::char_traits<char> > (13011F0h)   
  16. 01301055  add         esp,8   
  17. 01301058  mov         ecx,eax   
  18. 0130105A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)]   
  19. 01301060  jmp         wmain+64h (1301064h) ;構造完成,跳過下一句  
  20. 01301062  xor         esi,esi ;將esi置空,這裡的esi即為new A的返回值  
  21. 01301064  mov         dword ptr [esp+14h],0FFFFFFFFh   
  22.     delete a;  
  23. 0130106C  test        esi,esi ;檢查a是否為空  
  24. 0130106E  je          wmain+9Bh (130109Bh) ;如果為空,跳過解構函式和operator delete  
  25. 01301070  mov         edx,dword ptr [__imp_std::endl (1302038h)] ;解構函式 輸出字串  
  26. 01301076  mov         eax,dword ptr [__imp_std::cout (1302050h)]   
  27. 0130107B  push        edx    
  28. 0130107C  push        offset string "call A destructor" (1302148h)   
  29. 01301081  push        eax    
  30. 01301082  call        std::operator<<<std::char_traits<char> > (13011F0h)   
  31. 01301087  add         esp,8   
  32. 0130108A  mov         ecx,eax   
  33. 0130108C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)]   
  34. 01301092  push        esi  ;壓入a   
  35. 01301093  call        operator delete (13013BCh) ;呼叫operator delete   
  36. 01301098  add         esp,4   

通過反彙編可以確認A* = new A的三個步驟,delete a類似 
,包含了~A()和operator delete(a)兩個步驟。

二. operator new的三種形式

operator new有三種形式:

throwing (1)    
void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2) 
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3)   
void* operator new (std::size_t size, void* ptr) throw();

(1)(2)的區別僅是是否丟擲異常,當分配失敗時,前者會丟擲bad_alloc異常,後者返回null,不會丟擲異常。它們都分配一個固定大小的連續記憶體。

A* a = new A; //呼叫throwing(1)
A* a = new(std::nothrow) A; //呼叫nothrow(2)

(3)是placement new,它也是對operator new的一個過載,定義於#include <new>中,它多接收一個ptr引數,但它只是簡單地返回ptr。其在new.h下的原始碼如下:

#ifndef __PLACEMENT_NEW_INLINE
#define __PLACEMENT_NEW_INLINE
inline void *__cdecl operator new(size_t, void *_P)
        {return (_P); }
#if     _MSC_VER >= 1200
inline void __cdecl operator delete(void *, void *)
    {return; }
#endif
#endif

那麼它究竟有什麼用呢?事實上,它可以實現在ptr所指地址上構建一個物件(通過呼叫其建構函式),這在記憶體池技術上有廣泛應用。 
它的呼叫形式為:

new(p) A(); //也可用A(5)等有參建構函式

placement new本身只是返回指標p,new(p) A()呼叫placement new之後,還會在p上呼叫A:A(),這裡的p可以是動態分配的記憶體,也可以是棧中緩衝,如char buf[100]; new(buf) A(); 

我們仍然可以通過一個例子來驗證:

[cpp] view plain copy
  1. #include <iostream>  
  2. class A  
  3. {  
  4. public:  
  5.     A()  
  6.     {  
  7.         std::cout<<"call A constructor"<<std::endl;  
  8.     }  
  9.   
  10.     ~A()  
  11.     {  
  12.         std::cout<<"call A destructor"<<std::endl;  
  13.     }  
  14. };  
  15. int _tmain(int argc, _TCHAR* argv[])  
  16. {  
  17.   
  18.     A* p = (A*)::operator new(sizeof(A)); //分配  
  19.   
  20.     new(p) A(); //構造  
  21.   
  22.     p->~A();   //析構  
  23.   
  24.     ::operator delete(p); //釋放  
  25.   
  26.     system("pause");  
  27.     return 0;  
  28. }  

上面的程式碼將物件的分配,構造,析構和釋放分離開來,這也是new和delete關鍵字兩句就能完成的操作。 
先直接執行可以看到程式輸出:

call A constructor
call A destructor

再分別註釋掉new(a) A();和a->~A();兩句,可以看到對應的構造和解構函式將不會被呼叫。

然後檢視反彙編:

[cpp] view plain copy
  1. //平臺: Visual Studio 2008 Debug版  
  2.     A* a = (A*)::operator new(sizeof(A)); //分配  
  3. 00F9151D  push        1      
  4. 00F9151F  call        operator new (0F91208h) ;呼叫::operator new(size_t size)也就是throwing(1)版本  
  5. 00F91524  add         esp,4   
  6. 00F91527  mov         dword ptr [ebp-14h],eax ;返回地址放入[ebp-14h] 即為p  
  7.   
  8.     new(a) A(); //構造  
  9. 00F9152A  mov         eax,dword ptr [ebp-14h]   
  10. 00F9152D  push        eax    
  11. 00F9152E  push        1    ;壓入p  
  12. 00F91530  call        operator new (0F91280h);呼叫operator new(size_tvoid* p)即placement(3)版本 只是簡單返回p  
  13. 00F91535  add         esp,8   
  14. 00F91538  mov         dword ptr [ebp-0E0h],eax ;將p放入[ebp-0E0h]  
  15. 00F9153E  mov         dword ptr [ebp-4],0   
  16. 00F91545  cmp         dword ptr [ebp-0E0h],0   ;判斷p是否為空  
  17. 00F9154C  je          wmain+81h (0F91561h)     ;如果為空 跳過建構函式  
  18. 00F9154E  mov         ecx,dword ptr [ebp-0E0h] ;取出p到ecx  
  19. 00F91554  call        A::A (0F91285h)          ;呼叫建構函式 根據_thiscall呼叫約定 this指標通過ecx暫存器傳遞  
  20. 00F91559  mov         dword ptr [ebp-0F4h],eax ;將返回值(this指標)放入[ebp-0F4h]中  
  21. 00F9155F  jmp         wmain+8Bh (0F9156Bh)     ;跳過下一句  
  22. 00F91561  mov         dword ptr [ebp-0F4h],0   ;將[ebp-0F4h]置空 當前面判斷p為空時執行此語句  
  23. 00F9156B  mov         ecx,dword ptr [ebp-0F4h] ;[ebp-0F4h]為最終構造完成後的this指標(或者為空) 放入ecx  
  24. 00F91571  mov         dword ptr [ebp-0ECh],ecx ;又將this放入[ebp-0ECh] 這些都是除錯所用  
  25. 00F91577  mov         dword ptr [ebp-4],0FFFFFFFFh   
  26.   
  27.     a->~A();   //析構  
  28. 00F9157E  push        0      
  29. 00F91580  mov         ecx,dword ptr [ebp-14h] ;從[ebp-14h]中取出p  
  30. 00F91583  call        A::`scalar deleting destructor' (0F91041h) ;呼叫解構函式(跟蹤進去比較複雜 如果在Release下,構造解構函式都是直接展開的)  
  31.   
  32.     ::operator delete(a); //釋放  
  33. 00F91588  mov         eax,dword ptr [ebp-14h]   ;將p放入eax  
  34. 00F9158B  push        eax           ;壓入p  
  35. 00F9158C  call        operator delete (0F910B9h);呼叫operator delete(void* )  
  36. 00F91591  add         esp,4  

從反彙編中可以看出,其實operator new呼叫了兩次,只不過每一次呼叫不同的過載函式,並且placement new的主要作用只是將p放入ecx,並且呼叫其建構函式。 
事實上,在指定地址上構造物件還有另一種方法,即手動呼叫建構函式:p->A::A(); 這裡要加上A::作用域,用p->A::A();替換掉new(p) A();仍然能達到同樣的效果,反彙編:

[cpp] view plain copy
  1.     A* a = (A*)::operator new(sizeof(A)); //分配  
  2. 010614FE  push        1      
  3. 01061500  call        operator new (1061208h)   
  4. 01061505  add         esp,4   
  5. 01061508  mov         dword ptr [a],eax   
  6.   
  7.     //new(a) A();   //構造  
  8.     a->A::A();  
  9. 0106150B  mov         ecx,dword ptr [a]   
  10. 0106150E  call        operator new (1061285h)   
  11.   
  12.     a->~A();   //析構  
  13. 01061513  push        0      
  14. 01061515  mov         ecx,dword ptr [a]   
  15. 01061518  call        A::`scalar deleting destructor' (1061041h)   
  16.   
  17.     ::operator delete(a); //釋放  
  18. 0106151D  mov         eax,dword ptr [a]   
  19. 01061520  push        eax    
  20. 01061521  call        operator delete (10610B9h)   
  21. 01061526  add         esp,4  

比之前的方法更加簡潔高效(不需要呼叫placement new)。不知道手動呼叫建構函式是否有違C++標準或有什麼隱晦,我在其他很多有名的記憶體池(包括SGI STL alloc)實現上看到都是用的placement new,而不是手動呼叫建構函式。

三. operator new過載

前面簡單提到過A* p = new A;所發生的事情:先呼叫operator new,如果類A過載了operator new,那麼就使用該過載版本,否則使用全域性版本::operatro new(size_t size)

上面提到的throwing(1)和nothrow(2)的operator new是可以被過載的,比如:

[cpp] view plain copy
  1. #include <iostream>  
  2. class A  
  3. {  
  4. public:  
  5.     A()  
  6.     {  
  7.         std::cout<<"call A constructor"<<std::endl;  
  8.     }  
  9.   
  10.     ~A()  
  11.     {  
  12.         std::cout<<"call A destructor"<<std::endl;  
  13.     }  
  14.     void* operator new(size_t size)  
  15.     {  
  16.         std::cout<<"call A::operator new"<<std::endl;  
  17.         return malloc(size);  
  18.     }  
  19.   
  20.     void* operator new(size_t size, const std::nothrow_t& nothrow_value)  
  21.     {  
  22.         std::cout<<"call A::operator new nothrow"<<std::endl;  
  23.         return malloc(size);  
  24.     }  
  25. };  
  26. int _tmain(int argc, _TCHAR* argv[])  
  27. {  
  28.     A* p1 = new A;  
  29.     delete p1;  
  30.   
  31.     A* p2 = new(std::nothrow) A;  
  32.     delete p2;  
  33.   
  34.     system("pause");  
  35.     return 0;  
  36. }  

執行結果:

call A::operator new
call A constructor
call A destructor
call A::operator new nothrow
call A constructor
call A destructor

如果類A中沒有對operator new的過載,那麼new A和new(std::nothrow) A; 都將會使用全域性operator new(size_t size)。可將A中兩個operator new註釋掉,並且在A外新增一個全域性operator new過載:

void* ::operator new(size_t size)
{
    std::cout<<"call global operator new"<<std::endl;
    return malloc(size);
}

程式輸出:

call global operator new
call A constructor
call A destructor
call global operator new
call A constructor
call A destructor

注意,這裡的過載遵循作用域覆蓋原則,即在裡向外尋找operator new的過載時,只要找到operator new()函式就不再向外查詢,如果引數符合則通過,如果引數不符合則報錯,而不管全域性是否還有相匹配的函式原型。比如如果這裡只將A中operator new(size_t, const std::nothrow_t&)刪除掉,就會報錯:

error C2660: “A::operator new”: 函式不接受 2 個引數。

對operator new的過載還可以新增自定義引數,如在類A中新增

void* operator new(size_t size, int x, int y, int z)
{
    std::cout<<"X="<<x<<"  Y="<<y<<" Z="<<z<<std::endl;
    return malloc(size);
}

這種過載看起來沒有什麼大作用,因為它operator new需要完成的任務只是分配記憶體,但是通過對這類過載的巧妙應用,可以讓它在動態分配記憶體除錯和檢測中大展身手。這將在後面operator new過載運用技巧中展現。

至於placement new,它本身就是operator new的一個過載,不需也儘量不要對它進行改寫,因為它一般是搭配 new(p) A(); 工作的,它的職責只需簡單返回指標。

四. operator new運用技巧和一些例項探索

1. operator new過載運用於除錯:

前面提到如何operator new的過載是可以有自定義引數的,那麼我們如何利用自定義引數獲取更多的資訊呢,這裡一個很有用的做法就是給operator new新增兩個引數:char* file, int line,這兩個引數記錄new關鍵字的位置,然後再在new時將檔名和行號傳入,這樣我們就能在分配記憶體失敗時給出提示:輸出檔名和行號。

那麼如何獲取當前語句所在檔名和行號呢,windows提供兩個巨集:__FILE__和__LINE__。利用它們可以直接獲取到檔名和行號,也就是 new(__FILE__, __LINE__) 由於這些都是不變的,因此可以再定義一個巨集:#define new new(__FILE__, __LINE__)。這樣我們就只需要定義這個巨集,然後過載operator new即可。

原始碼如下,這裡只是簡單輸出new的檔名和行號。

[cpp] view plain copy
  1. //A.h  
  2. class A  
  3. {  
  4. public:  
  5.     A()  
  6.     {  
  7.         std::cout<<"call A constructor"<<std::endl;  
  8.     }  
  9.   
  10.     ~A()  
  11.     {  
  12.         std::cout<<"call A destructor"<<std::endl;  
  13.     }  
  14.   
  15.     void* operator new(size_t size, const char* file, int line)  
  16.     {  
  17.         std::cout<<"call A::operator new on file:"<<file<<"  line:"<<line<<std::endl;  
  18.         return malloc(size);  
  19.         return NULL;  
  20.     }  
  21.   
  22. };  
  23. //Test.cpp  
  24. #include <iostream>  
  25. #include "A.h"  
  26. #define new new(__FILE__, __LINE__)  
  27.   
  28. int _tmain(int argc, _TCHAR* argv[])  
  29. {  
  30.     A* p1 = new A;  
  31.     delete p1;  
  32.   
  33.     system("pause");  
  34.     return 0;  
  35. }  

輸出:

call A::operator new on file:d:\desktop\test\test.cpp line:8
call A constructor
call A destructor

注意:需要將類的宣告實現與new的使用隔離開來。並且將類標頭檔案放在巨集定義之前。否則在類A中的operator new過載中的new會被巨集替換,整個函式就變成了:void* operator new(__FILE__, __LINE__)(size_t size, char* file, int line),編譯器自然會報錯。

2. 記憶體池優化

operator new的另一個大用處就是記憶體池優化,記憶體池的一個常見策略就是分配一次性分配一塊大的記憶體作為記憶體池(buffer或pool),然後重複利用該記憶體塊,每次分配都從記憶體池中取出,釋放則將記憶體塊放回記憶體池。在我們客戶端呼叫的是new關鍵字,我們可以改寫operator new函式,讓它從記憶體池中取出(當記憶體池不夠時,再從系統堆中一次性分配一塊大的),至於構造和析構則在取出的記憶體上進行,然後再過載operator delete,它將記憶體塊放回記憶體池。關於記憶體池和operator new在參考文獻中有一篇很好的文章。這裡就不累述了。

3. STL中的new

在SGI STL原始碼中,defalloc.h和stl_construct.h中提供了最簡單的空間配置器(allocator)封裝,見《STL原始碼剖析》P48。它將物件的空間分配和構造分離開來,雖然在defalloc.h中僅僅是對::operator new和::operator delete的一層封裝,但是它仍然給STL容器提供了更加靈活的介面。SGI STL真正使用的並不是defalloc.h中的分配器,而是stl_alloc.h中的SGI精心打造的"雙層級配置器",它將記憶體池技術演繹得淋漓盡致,值得細細琢磨。順便提一下,在stl_alloc.h中並沒有使用::operator new/delete 而直接使用malloc和free。具體緣由均可參見《STL原始碼剖析》。

五. delete的使用

delete的使用基本和new一致,包括operator delete的過載方式這些都相似,只不過它的引數是void*,返回值為void。但是有一點需要注意,operator delete的自定義引數過載並不能手動呼叫。比如

[cpp] view plain copy
  1. void* operator new(size_t size, int x)  
  2. {  
  3.     cout<<" x = "<<x<<endl;  
  4.     return malloc(size);      
  5. }  
  6. void operator delete(void* p, int x)  
  7. {  
  8.     cout<<" x = "<<x<<endl;  
  9.     free(p);  
  10. }  

如下呼叫是無法通過的:

A* p = new(3) A;//ok
delete(3) p;//error C2541: “delete”: 不能刪除不是指標的物件

那麼過載operator delete有什麼作用?如何呼叫?事實上以上自定義引數operator delete 只在一種情況下被呼叫:當new關鍵字丟擲異常時。

可以這樣理解,只有在new關鍵字中,編譯器才知道你呼叫的operator new形式,然後它會呼叫對應的operator delete。一旦出了new關鍵字,編譯器對於這塊記憶體是如何分配的一無所知,因此它只會呼叫預設的operator delete,而至於為什麼不能主動呼叫自定義delete(而只能老老實實delete p),這個就不知道了。

細心觀察的話,上面operator new用於除錯的例子程式碼中,由於我們沒有給出operator new對應的operator delete。在VS2008下會有如下警告:

warning C4291: “void *A::operator new(size_t,const char *,int)”: 未找到匹配的刪除運算子;如果初始化引發異常,則不會釋放記憶體

六. 關於new和記憶體分配的其他

1. set_new_handler

還有一些零散的東西沒有介紹到,比如set_new_handler可以在malloc(需要呼叫set_new_mode(1))或operator new記憶體分配失敗時指定一個入口函式new_handler,這個函式完成自定義處理(繼續嘗試分配,丟擲異常,或終止程式),如果new_handler返回,那麼系統將繼續嘗試分配記憶體,如果失敗,將繼續重複呼叫它,直到記憶體分配完畢或new_handler不再返回(丟擲異常,終止)。下面這段程式完成這個測試:

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <new.h>// 使用_set_new_mode和set_new_handler  
  3. void nomem_handler()  
  4. {  
  5.     std::cout<<"call nomem_handler"<<std::endl;  
  6. }  
  7. int main()  
  8. {  
  9.     _set_new_mode(1);  //使new_handler有效  
  10.     set_new_handler(nomem_handler);//指定入口函式 函式原型void f();  
  11.     std::cout<<"try to alloc 2GB memory...."<<std::endl;  
  12.     char* a = (char*)malloc(2*1024*1024*1024);  
  13.     if(a)  
  14.         std::cout<<"ok...I got it"<<std::endl;  
  15.     free(a);  
  16.     system("pause");  
  17. }  

程式執行後會一直輸出call nomem_handler 因為函式裡面只是簡單輸出,返回,系統嘗試分配失敗後,呼叫nomem_handler函式,由於該函式並沒有起到實際作用(讓可分配記憶體增大),因此返回後系統再次嘗試分配失敗,再呼叫nomem_handler,迴圈下去。 
在SGI STL中的也有個仿new_handler函式:oom_malloc

2. new分配陣列

new[]和new類似,仍然會優先呼叫類中過載的operator new[]。另外還要注意的是,在operator new[](size_t size)中傳入的並不是sizeof(A)*3。而要在物件陣列的大小上加上一個額外資料,用於編譯器區分物件陣列指標和物件指標以及物件陣列大小。在VS2008(32 bit)下這個額外資料佔4個位元組,一個int大小。測試程式碼如下

[cpp] view plain copy
  1. //A.h  
  2. class A  
  3. {  
  4. public:  
  5.     A()  
  6.     {  
  7.         std::cout<<"call A constructor"<<std::endl;  
  8.     }  
  9.   
  10.     ~A()  
  11.     {  
  12.         std::cout<<"call A destructor"<<std::endl;  
  13.     }  
  14.   
  15.     void* operator new[](size_t size)  
  16.     {  
  17.         std::cout<<"call A::operator new[] size:"<<size<<std::endl;  
  18.         return malloc(size);  
  19.     }  
  20.     void operator delete[](void* p)  
  21.     {  
  22.         std::cout<<"call A::operator delete[]"<<std::endl;  
  23.         free(p);  
  24.     }   
  25.     void operator delete(void* p)  
  26.     {  
  27.         free(p);  
  28.     }   
  29. };  
  30. //Test.cpp  
  31. #include <iostream>  
  32. #include "A.h"  
  33.   
  34. void* operator new[](size_t size)  
  35. {  
  36.     std::cout<<"call global new[] size: "<<size<<std::endl;  
  37.     return malloc(size);  
  38. }  
  39.   
  40. void operator delete[](void* p)  
  41. {  
  42.     std::cout<<"call global delete[] "<<std::endl;  
  43. }  
  44. int _tmain(int argc, _TCHAR* argv[])  
  45. {  
  46.     std::cout<<"sizeof A "<<sizeof(A)<<std::endl;  
  47.     A* p1 = new A[3];  
  48.     delete []p1;  
  49.   
  50.     system("pause");  
  51.     return 0;  
  52. }  

輸出:

sizeof A 1
call global new[] size: 7
call A constructor
call A constructor
call A constructor
call A destructor
call A destructor
call A destructor
call A::operator delete[]

簡單跟蹤了一下,operator new[]返回的是0x005b668 而最後new關鍵字返回給p的是0x005b66c。也就是說p就是陣列的起始地址,這樣程式看到的記憶體就是線性的,不包括前面的額外資料。

在記憶體中,可以看到前面的四個位元組額外資料是0x00000003 也就是3,代表陣列元素個數。後面三個cd是堆在Debug中的預設值(中文的cdcd就是”屯”,棧的初始值為cc,0xcccc中文”燙”)。再後面的0xfdfdfdfd應該是堆塊的結束標誌,前面我有部落格專門跟蹤過。

注:其實在malloc原始碼中也有記憶體池的運用,而且也比較複雜。最近在參考dlmalloc版本和STL空間介面卡,真沒有想到一個記憶體分配能涉及這麼多的東西。

七. 參考文獻:

1.http://www.cplusplus.com/reference/new/operator%20new/?kw=operator% operator new的三種形式 
2.http://www.relisoft.com/book/tech/9new.html c++ operator new過載和記憶體池技術 
3.《STL原始碼剖析》 空間配置器 
4. http://blog.csdn.net/songthin/article/details/1703966 一篇關於理解C++ New的好文 
5. http://blog.csdn.net/solstice/article/details/6198937 陳碩的Blog

轉載請註明出處:http://blog.csdn.net/wudaijun/article/details/9273339


相關文章