[GeekBand] C++ 內存分佈—— new和delete重載的實現及分析

本文參考文獻:GeekBand課堂內容,授課老師:侯捷html

                 :深度探索C++對象模型(侯捷譯)數組

                 :網絡資料: http://www.leavesite.com/geekband-cpp-5.html網絡

                                   http://blog.csdn.net/wudaijun/article/details/9273339函數

 

本週的課題是:「 爲上週題目中的 Fruit和Apple 添加 構造函數與 析構函數, 並在構造函數與析構函數中打印控制檯信息,觀察構造和析枸調用過程。而後爲Apple類重載::operator new和 ::operator delete,在控制檯打印信息,並觀察調用結果。」測試

      雖然其中構造與析構調用過程上次代碼裏已經實現了,而且比如今這份還要完善一些。但作爲開場白再講一遍比較好!ui

         

     首先,先看下 類的結構。Apple 類繼承自基類Fruitthis

 1 //基類
 2 class Fruit
 3 {
 4 public:
 5     //使用自帶的構造函數
 6     Fruit()
 7     {
 8         cout << "Call Fruit Constructor.this =  " <<this<< endl;
 9     }
10     //打印變量內存地址
11     void print(){}
12     //虛函數的影響
13     virtual void process(){}
14 
15     virtual ~Fruit()
16     {
17         cout << "Call Fruit Destructor = " << this << endl; 
18     }
19 
20 private:
21     int no;
22     double weight;
23     char key;
24 };
25 
26 //這裏考慮本身自己的虛函數,及基類的虛函數
27 class Apple : public Fruit
28 {
29 public:
30     //使用默認的構造函數
31     Apple()
32     {
33         cout << "Call Apple Constructor.this =  " << this << endl;
34     };
35     //打印成員數據
36     void save(){}
37     virtual void process(){}
38     virtual ~Apple()
39     {
40         cout << "Call Apple Destructor.this = " << this << endl;
41     }
42 
43     //測試2、拋出異常
44     static void* operator new(size_t size);
45     //測試3、沒有拋出異常,此版本須要註釋掉測試二
46     //static void* operator new(size_t size, const std::nothrow_t& nothrow_value);
47     
48     //測試4、帶有調試信息的版本,此版本須要註釋掉測試2、測試三
49     //inline void* Apple::operator new(size_t size, const char* file, int line);
50     
51     //delete 版本
52     static void  operator delete(void* ptr, size_t size) throw();
53 
54     //測試5、測試數組
55     static void *operator new[](size_t size);
56     static void operator delete[](void *ptr);
57 
58 private:
59     int size;
60     char type;
61 };

 

      那麼問題來了,Apple 類和Fruit誰先構造、又誰先析構呢?進而思考,基類和子類誰更大一些?spa

衆所周知,子類擁有父類的一切信息,並且子類有些信息更具體,好比鳥都有翅膀,這是共性。可是好比啄木鳥的嘴特別長,這就是特性。天然界是共性與特性的統一。.net

不過從哲學的角度來看,如「人是社會關係的總和」,講的也是這個道理。3d

 

扯得有點遠了,看圖!因此構造時先構造內部,而後構造外部,析構時正好相反!

                                               

                        

能夠充分證實這個觀點,還有問題的話,拷貝我上篇blog代碼,能夠有更詳細的分析,這裏就不展開講了。畢竟只是開場白!

 

1、new和delete重載的實現及分析

      new:指咱們在C++裏一般用到的運算符,好比A* a = new A;  對於new來講,有new和::new之分,前者位於std
      operator new():指對new的重載形式,它是一個函數,並非運算符。對於operator new來講,分爲全局重載和類重載,全局重載是void* ::operator new(size_t size),在類中重載形式 void* A::operator new(size_t size)。還要注意的是這裏的operator new()完成的操做通常只是分配內存,事實上系統默認的全局::operator new(size_t size)也只是調用malloc分配內存,而且返回一個void*指針。而構造函數的調用(若是須要)是在new運算符中完成的。

                                                                                                                                      -----------wudaijun  blog

 

一、重載時,一個類爲空怎麼處理?

       一個類中,若是什麼數據都沒有!打印結果倒是1

class Empty
{
};

int main(int argc, char** argv)
 {
    std::cout << sizeof(Empty) << std::endl;
    return 0;
}

 

 

因此咱們爲類進行new 重載時應該也要考慮到這一點。至於爲何是1,不是0,也而不是其餘的數據。我沒弄清楚。但根據調試結果來分析,

咱們在重載應該考慮到這一點。

首先應該判斷下size是否爲0。有指針時也要判斷指針是否爲空。

inline void* Apple::operator new(size_t size)
{
    if (size == 0)
    {
        return malloc(1);
    }
    void *ptr = malloc(size);
    if (ptr)
    {
        cout << "Apple::size = " << size << "    Apple::Address  =  " << ptr << endl;
        return (Apple*)ptr;
    }

    else 
    {
        throw bad_alloc();
    }
}

 

二、operator  new() 和 operator delete() 會自動轉換爲static 成員

       由葉卡同窗的blog中記錄的 C++ Primer 557所示,成員operator new() 和 operator delete()會自動成爲static成員。

所以,它們沒有this指針,並且也不會修改物件內容,僅僅做爲開闢空間、和清楚空間的做用!

三、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();

 

第一、2種的區別 是有無拋出異常,其中有拋出異常的還能夠進一步拋出信息,下面將會分析。

第3種 placement new,它也是對operator new的一個重載,定義於<new>中,它多接收一個ptr參數,但它只是簡單地返回ptr。這裏暫時沒有詳細分析,請同窗自行查閱資料。(我上面的推薦資料裏就有)

/*
測試2、拋出異常的版本
*/

inline void* Apple::operator new(size_t size)
{
    if (size == 0)
    {
        return malloc(1);
    }
    void *ptr = malloc(size);
    if (ptr)
    {
        cout << "Apple::size = " << size << "    Apple::Address  =  " << ptr << endl;
        return (Apple*)ptr;
    }

    else 
    {
        throw bad_alloc();
    }
}

 

運行圖以下:

 

 從上圖分析得出,Fruit的Size爲32,Apple 的Size爲40。與上述相對應。

 

/*
測試3、沒有拋出異常的版本
*/


inline void* Apple:: operator new(size_t size, const std::nothrow_t& nothrow_value)
{
	//即便是空類,大小也爲1
	if (size == 0)
	{
		return malloc(1);
	}
	else
		std::cout << "call Apple::operator new nothrow" << std::endl;
		return malloc(size);
}

  

 這個版本是沒有返回異常信息的版本

 

如圖所示,New的過程當中那些打印信息並無顯示。

 

new 這類信息每每會用在調試代碼階段。能比較方便的顯示出行數及文件信息。

*
    測試4、拋出異常,並帶有調試信息的版本
    此版本使用時,會對以上兩個版本發生衝突,須要註釋掉另外兩個函數,及使用
*/


inline void* Apple::operator new(size_t size, const char* file, int line)
{
    //即便是空類,大小也爲1
    if (size == 0)
    {
        return malloc(1);
    }
    void *ptr = malloc(size);
    if (ptr)
    {
        std::cout << "call A::operator new on file:" << file << "  line:" << line << std::endl;
        cout << "Apple::size = " << size << "    Apple::Address  =  " << ptr << endl;
        return (Apple*)ptr;
    }

    else {
        throw bad_alloc();
    }
}

 

在測試頭部也要添加信息

 

//測試4、打開註釋
//#define new new(__FILE__, __LINE__)  

 

 

 

如圖所示,顯示了文件、及行數信息,方便調試。

 

 

四、什麼時候重載類中、全局的 operator  new() 

 

    /*
        測試1、棧空間,使用自帶的new 和全局new
    */
    
    Apple ptrApple;


    Fruit *ptr = new Fruit();
    delete ptr;
    Apple* ptr1 = new Apple();//Apple 是臨時變量,所佔空間是以new動態分配而得,並由p指向,佔用空間爲堆
    delete ptr1;
    

 

 

這裏有兩種方法使用Apple 類,第一種爲棧調用的方法,第二種爲堆調用的方法(本身malloc)。這兩種方法調用new 和delete的位置不一樣。

如圖所示, 這裏實際上有幾個步驟:

一、分配內存.

二、指針類型轉換

三、調用構造函數

 

       分配內存這一操做就是由operator new(size_t)來完成的,若是類A重載了operator new,那麼將調用A::operator new(size_t ),若是沒有重載,就調用::operator new(size_t ),

 

 

     經過以上結果對比,做用域覆蓋原則,即在裏向外尋找operator new的重載時,只要找到operator new()函數就再也不向外查找,若是參數符合則經過,若是參數不符合則報錯,而無論全局是否還有相匹配的函數原型。

      既先查找類中的operator new()和 operator  delete(),而後再執行全局operator new()和 operator  delete()。

 

五、多維數組的重載

 

/*
    測試5、類中重載new[] 和 delete[]
*/
inline void* Apple::operator new[](size_t size)
{
    //即便是空類,大小也爲1
    if (size == 0)
    {
        return malloc(1);
    }
    cout << "This is Apple New[]! Now allocating space :" << size << "Byte!" << endl;
    return malloc(size);
}

inline void Apple::operator delete[](void *ptr)
{
    if (ptr)
    {
        cout << "This is Apple Delete[], Now free space!" << endl;
        free(ptr);
    }
    else
    {
        ptr = NULL;
    }
}

 

 

    Apple *ptr3 = new Apple[3];
    cout << "ptr3[0] addr: " << ptr3 << endl;
    cout << "ptr3[1] addr: " << ptr3 + 1 << endl;
    cout << "ptr3[2] addr: " << ptr3 + 2 << endl;
    delete[] ptr3;
    ptr3 = NULL;

 

 

 

下面用圖來解釋下,(此圖源於某blog內容,後面圖保存了,卻找不到來源,請做者勿怪,若有侵權,請聯繫我,謝謝)

 

 

 

 

 delete的過程

 

 

 

                                                                                                                         煩請路過的朋友,批評指針。感謝網絡的無私奉獻者。                                                                                                                                                                                                  修改於   2016.08.15  17:28

                  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

內容修改中,8月15日晚11:30分前

上傳最新版本 

相關文章
相關標籤/搜索