1. 程式人生 > >淺談new、delete

淺談new、delete

首先我們你知道C語言中使用malloc、calloc、realloc和free進行動態記憶體管理。
malloc、calloc、realloc用來在堆上開闢空間, free將申 請的空間釋放掉。

在C++中這些都還是可以用的,但是我們用到更多的還是new和delete,new[]和delete[];
注意在這裡兩個組合一定要匹配使用。

一、new、delete.
1.new
首先我們隨便寫一個程式碼
int* p=new int
然後我們看他的反彙編
這裡寫圖片描述
分析它先給我們壓棧4byte的引數後再呼叫operator new。原始碼如下:
注意:這裡的operator new和operator函式不同(比如operator =),這函式沒有過載.

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                        _THROW_NCEE(_XSTD bad_alloc, );
                }

        return
(p); }

分析operator new原始碼:如果malloc呼叫失敗,也就是記憶體申請失敗就會呼叫_callnewh()這個函式
這個callnewh()是什麼呢?它是一個new handler,通俗來講就是new失敗的時候呼叫的回撥函式。

namespace std {

#ifdef _M_CEE_PURE
typedef void (__clrcall * new_handler) ();
#else  /* _M_CEE_PURE */
typedef void (__cdecl * new_handler) ();
#endif  /* _M_CEE_PURE */
#ifdef _M_CEE typedef void (__clrcall * _new_handler_m) (); #endif /* _M_CEE */ _CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler _NewHandler) throw(); };

這裡:
1、呼叫一個客戶指定的錯誤處理函式——一個new-handler。
2、丟擲異常bad_alloc(【舊】返回一個null指標)。
可以通過_set_new_handler來設定。
客戶定義的錯誤處理函式new handler應該什麼樣子?

void My_NewHandler()//沒有引數也不返回任何值的函式
{
    std::cerr << "Unable to satisfy request for memory\n";//提示
    //1讓更多記憶體可用——程式開始執行就分配一塊記憶體,當new-handler第一次被呼叫,將它們釋還給程式使用。
    //2呼叫std::set_new_handler呼叫另一個new_handler函式。或者直接修改自身,比如改static資料,全域性資料,namespace資料。
    //3丟擲bad_alloc異常(或派生),異常不會被operatornew捕捉,會被傳播到記憶體索求處。
    //4不返回,呼叫abort或者exit終止程式。
    std::abort();//終止程式
}
void main()  
{   
    _set_new_handler(My_NewHandler);  

    while (1)  
    {  
        int* p = new int[10000000];  
    }  
}  

另外的如果在類中,new還會呼叫建構函式,而malloc就不會呼叫建構函式
這裡寫圖片描述

這裡寫圖片描述
相同的delete p會呼叫解構函式,而free不呼叫.
總結:
{
1.呼叫operator new分配空間。
呼叫建構函式初始化物件。
2.new和delete他們只 負責分配空間/釋放空間, 不會呼叫物件建構函式/解構函式來初始化/清理物件。
3.實際operator new和operator delete只是malloc和free的一層封裝。
4.new分配失敗的時候不像malloc那樣返回NULL,它直接丟擲異常。要判斷是否分配成功應該用異常捕獲的機制;
}

2.delete.
先來看反彙編,和new原理幾乎相同
這裡寫圖片描述
這裡寫圖片描述
呼叫解構函式清理物件
再呼叫operator delete釋放空間
下面是operator delete實現程式碼:

void operator delete( void * p )
{
    RTCCALLBACK(_RTC_Free_hook, (p, 0));

    free( p );
}

RTCCALLBACK預設是空的巨集定義,所以這個函式預設情況下就是簡單的呼叫free函式。
總結:
delete簡單資料型別預設只是呼叫free函式。
總結步驟圖:
這裡寫圖片描述
二new[]、delete[]。
1.new[](類的陣列)
首先我們可以看反彙編
這裡寫圖片描述
然後我們F11進去
這裡寫圖片描述
再F11
這裡寫圖片描述
這時我們可以看到和new一樣最後還是呼叫的operator new。
再看constructor 這個進去之後是呼叫建構函式.
這裡寫圖片描述
仔細看這裡的彙編它先給我們push進去一個 2Ch 2Ch是多少呢?是44
我們再來看看這個圖
這裡寫圖片描述
很明顯這裡的count是編譯器給我們申請的位元組數,如果你開啟監視,你會發現這裡的count就是44
這裡的ecx就是申請到的空間地址
我們可以看到其中push 0Ah=10;
開啟記憶體視窗可以看到,p地址的上一個地址正好是a也就是10;
這裡寫圖片描述
這就證明,給ecx+4,也就是ecx指標偏移四個位元組,最後將偏移後ecx給p,這裡才是使用者拿到的地址.
這裡就有很多疑惑,我們只需要10*4=40個位元組為什麼要給我們多申請四個位元組呢?
為什麼要給申請到的地址先壓棧一個10,然後再把偏移四個位元組之後地址的給使用者使用呢。對類型別,

delete一個數組時(比如,delete []sa;),要為每一個數組元素呼叫解構函式。但對於delete表示式(比如,這裡的delete []p,它並不知道陣列的元素個數(只有new函式和delete函式知道)。因此,必須有一種手段來告訴delete表示式的陣列大小是多少。那麼一種可行的方式就是,多分配一個大小為4位元組的空間來記錄陣列大小,並可以約定前四位元組來記錄大小。那麼,由new函式分配的地址與new表示式返回的地址應該相差4個位元組(這可以寫程式來驗證)。對於非類型別陣列和不需要呼叫解構函式的類型別陣列,這多於的四位元組就不需要了。

總結:
呼叫operator new分配空間。
呼叫N次建構函式分別初始化每個物件。
多申請四個位元組用來存放陣列大小
2。delete[]
這裡就不分析delete[]了和前面的分析方法差不多可以自己動手看看反彙編。
這裡寫圖片描述
/*****/
一個進擊的學生
/****/