[GeekBand] C++繼承關係下虛函數內存分佈

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

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

                 :網絡資料,如:http://blog.csdn.net/sanfengshou/article/details/4574604
框架

 

    說明:因爲條件限制,僅測試了Windows平臺下的VS2013 IDE。其他平臺結果可能不一樣,但原理都相似。建議讀者本身在其餘平臺進行測試。函數

一、什麼是虛函數?

虛函數是類的非靜態成員函數,在類中的基本形式以下:virtual 函數返回值類型 虛函數名(形參表)工具

如:virtual void process()學習

二、虛函數的做用,爲何採用虛函數?

    虛函數的做用是實現動態聯編,也就是在程序的運行階段動態地選擇合適的成員函數,在定義了虛函數後,能夠在基類的派生類中對虛函數從新定義(形式也是:virtual 函數返回值類型 虛函數名(形參表){ 函數體 }),在派生類中從新定義的函數應與虛函數具備相同的形參個數和形參類型。以實現統一的接口,不一樣定義過程。若是在派生類中沒有對虛函數從新定義,則它繼承其基類的虛函數。測試

三、正文

  首先,假設有一個Fruit類,以下所示:ui

//基類
class Fruit
{
public:
    //構造函數
    Fruit(const int no_,const double weight_,const char key_) :no(no_), weight(weight_), key(key_){};
    //打印變量內存地址
    void print() 
    {
        cout << "no :" << no << "           |    " << " Memory Address no: " << (void*)&no << endl;
        cout << "weight :" << weight << "       |    " << "  Memory Address weight: " << (void*)&weight << endl;
        cout << "key :" << key << "          |    " << "  Memory Address key: " << (void*)&key << endl;
    }
    //虛函數的影響
    virtual void process()
    { 
        cout << "the Process function of Base Fruit is called!" << endl;
    }
private:
    int no;
    double weight;
    char key;
};

 

咱們知道,int類型爲4個字節,double類型爲8個字節,char類型爲1個字節,又知道Class中存在着字節對齊的說法。這裏有流傳比較廣的三原則:this

一、偏移地址和成員佔用大小均須要對齊;spa

二、結構體做爲成員:若是一個結構裏有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲.(struct a裏存有struct b,b裏有char,int ,double等元素,那b應該從8的整數倍開始存儲.)

三、結構體的總大小,也就是sizeof的結果,.必須是其內部最大成員的整數倍.不足的要補齊.

由此能夠推斷出,此時數據段大小應爲24字節

若是數據更換位置了呢?好比,感興趣的朋友本身驗證下:

private:
    char key;
    int no;
    double weight;

 

最後,相信你們都知道 #pragma pack(),這個函數。這裏面又有着效率等問題,之後再詳細的分析它,因爲與此標題無關,就不展開說了。

 

測試代碼以下所示:

 

#include "stdafx.h"
#include"Object.h"
#include"iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    //基類(水果):測試數據
    cout << "------------------------------------------------------------------" << endl;
    cout << "The Memory Test Data of Fruit Class !"<< endl;
    cout << "------------------------------------------------------------------" << endl;
    Fruit TestFruit(0,0.0,'b');
    cout << "Fruit Class Size : " << sizeof(Fruit) << endl;                               //類的大小
    cout << "Fruit Memory Layout FirstAddress: " << (void*)&TestFruit << endl;            //類的首地址
    //虛函數表的地址存在在前四個字節中
    cout << "Fruit Virtual Function Table Address:" << (int*)(&TestFruit) << endl;       //虛函數表地址
    //打印類的信息
    TestFruit.print();                                                                    //類中成員的地址信息
    cout << "Fruit Virtual process Function Address: " << (int*)*(int*)&TestFruit + 0 << endl;  //虛函數表中函數process函數首地址
    // 經過函數指針調用函數,驗證正確性
    typedef void(*func_pointer)(void);
    func_pointer fptr = NULL;
    fptr = (func_pointer)*((int*)*(int*)&TestFruit + 0); // v_func1()
    fptr(); //process 
}

 

結果以下所示:

 

根據顯示結果,因此咱們能夠大體的畫出內存分佈圖:

其中

一、Fruit類爲類,大小爲 sizeof(Fruit) = 32,

二、vptr 爲虛指針,指向了虛函數表

三、int類型爲4字節,爲了對齊,因此填充了4個字節。同理,char填充了7個字節。

          

若是派生類和基類有共同的虛函數時內存如何分佈呢?

子類代碼以下,注意此時子類和基類都有virtual void process()函數

 
#ifndef  _OBJECT_H_
#define  _OBJECT_H_
#include"iostream"
using namespace std;
//基類
class Fruit
{
public:
    //構造函數
    Fruit(const int no_,const double weight_,const char key_) :no(no_), weight(weight_), key(key_){};
    //打印變量內存地址
    void print() 
    {
        cout << "no :" << no << "           |    " << " Memory Address no: " << (void*)&no << endl;
        cout << "weight :" << weight << "       |    " << "  Memory Address weight: " << (void*)&weight << endl;
        cout << "key :" << key << "          |    " << "  Memory Address key: " << (void*)&key << endl;
    }
    //虛函數的影響
    virtual void process()
    { 
        cout << "the Process function of Base Fruit is called!" << endl;
    }
private:
    int no;
    double weight;
    char key;
};

//派生類
//這裏考慮本身自己的虛函數,及基類的虛函數
class Apple : public Fruit
{
public:
    //構造函數
    Apple(const Fruit& fruit_,const int size_,const char type_) :size(size_), type(type_),Fruit(fruit_){};
    //打印成員數據
    void save() 
    {
        cout << "size :" << size << "   |    " << "   Apple Memory Address no: " << (void*)&size << endl;
        cout << "type :" << type << "   |    " << "   Apple Memory Address weight: " << (void*)&type << endl;
    }
    virtual void process()
    {
        cout << "the Process function of Derived Apple is called!" << endl;
    }
private:
    int size;
    char type;
};

#endif

 

完整測試代碼以下:

 

// TestObjectSize.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include"Object.h"
#include"iostream"
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    //基類(水果):測試數據
    cout << "------------------------------------------------------------------" << endl;
    cout << "The Memory Test Data of Fruit Class !"<< endl;
    cout << "------------------------------------------------------------------" << endl;
    Fruit TestFruit(0,0.0,'b');
    cout << "Fruit Class Size : " << sizeof(Fruit) << endl;                               //類的大小
    cout << "Fruit Memory Layout FirstAddress: " << (void*)&TestFruit << endl;            //類的首地址
    //虛函數表的地址存在在前四個字節中
    cout << "Fruit Virtual Function Table Address:" << (int*)(&TestFruit) << endl;       //虛函數表地址
    //打印類的信息
    TestFruit.print();                                                                    //類中成員的地址信息
    cout << "Fruit Virtual process Function Address: " << (int*)*(int*)&TestFruit + 0 << endl;  //虛函數表中函數process函數首地址
    // 經過函數指針調用函數,驗證正確性
    typedef void(*func_pointer)(void);
    func_pointer fptr = NULL;
    fptr = (func_pointer)*((int*)*(int*)&TestFruit + 0); // v_func1()
    fptr(); //process 

    //派生類(蘋果):測試數據
    cout << "------------------------------------------------------------------" << endl;
    cout << "The Memory Test Data of Apple Class  !" << endl;
    cout << "------------------------------------------------------------------" << endl;
    Apple TestApple(TestFruit, 1, 't');
    cout << "Apple Class Size : " << sizeof(Apple) << endl;                               //類的大小
    cout << "Apple Memory Layout FirstAddress: " << (void*)&TestApple << endl;            //類的首地址
    //虛函數表的地址存在在前四個字節中
    cout << "Apple Virtual Function Table Address:" << (int*)(&TestApple) << endl;       //虛函數表地址
    //查看基類Fruit類的信息
    TestApple.print();
    //打印Apple類的信息
    TestApple.save();                                                                    //類中成員的地址信息
    cout << "Apple Virtual process Function Address: " << (int*)*(int*)&TestApple + 0 << endl;  //虛函數表中函數process函數首地址
    // 經過函數指針調用函數,驗證正確性
    typedef void(*func_pointer2)(void);
    func_pointer fptr2 = NULL;
    fptr2 = (func_pointer2)*((int*)*(int*)&TestApple + 0); // v_func1()
    fptr2(); //process 

    system("pause");
    return 0;
}

 

咱們先看下實際結果:

 

 

經過結果,咱們能夠畫出此時子類的內存分佈圖。

根據顯示結果,咱們能夠進一步分析!

其中

一、Apple類爲派生類,大小爲 sizeof(Apple) = 40,

二、vptr 爲虛指針,指向了虛函數表.同時,先對基類進行了內存分配,而後再對子類進行內存分配。

三、派生類中int類型爲4字節,char 爲1字節。爲了對齊,char 類型填充了3個字節、

 

根據以上分析:總體框架如圖:

         

 

4.一、進一步的思考

一、 經過以上分析,咱們基本上知道了系統如何進行內存分佈的,咱們這裏對虛函數表內存進一步的觀察與思考:

打印出來的信息 : Fruit Virtual process Function Address : 001AEC78

                        Apple Virtual process Function Address :001AEDB0

                        觀察獲得,從內存角度出發,基類的地址(001AEC78)要小於子類(001AEDB0),這說明基類的虛函數在子類的前面。

 

二、分別在Fruit類、Apple類中添加虛函數:

 

    //測試用,非本題範圍
    virtual void process_b0()
    {
        cout << "This is Base Fruit class's process_b0" << endl;
    }

 

及Apple類中添加虛函數

    //測試用,非本題範圍
    virtual void process_b1()
    {
        cout << "This is Derived Apple_class's process_b1" << endl;
    }

 

三、而後修改測試代碼段,將檢驗一次,改成三次

 

 

    // 經過函數指針調用函數,驗證正確性
    //typedef void(*func_pointer2)(void);
    //func_pointer fptr2 = NULL;
    //fptr2 = (func_pointer2)*((int*)*(int*)&TestApple + 0); // v_func1()
    //fptr2(); //process 

    // 經過函數指針調用函數,驗證調用問題
    typedef void(*func_pointer)(void);
    func_pointer fp = NULL;
    for (int i = 0; i<3; i++) {
    fp = (func_pointer)*((int*)*(int*)&TestApple + i);
    fp();

 

四、運行後結果如圖所示:

 

經過結果進一步的思考與分析:

一、顯示:The Progress function of Deviced Apple is called !同名的process,運行的是子類的process()。說明此時用子類的虛函數process()代替了父類的虛函數process()!

二、先顯示:this is Base Fruit class proccess_b0 ,後顯示:This is Deviced Apple class process_b1.進一步說明了基類的虛函數內存分佈在子類的以前!

 

基本關係如圖所示:

 

4.二、調試技巧

在VS2013 DE中有不少方便的工具,能夠清晰的觀察出變量等各類信息,如圖經過debug模式下便可查看出不少關鍵信息!

 

二、經過反彙編觀察,這點還不是很熟,不過之後要增強調試經驗。

 

 

                                                                以上就是我我的的一些不成熟的學習筆記,望各位批評指正。謝

相關文章
相關標籤/搜索