[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模式下便可查看出不少关键信息!

 

二、经过反汇编观察,这点还不是很熟,不过之后要增强调试经验。

 

 

                                                                以上就是我我的的一些不成熟的学习笔记,望各位批评指正。谢

相关文章
相关标签/搜索