首页 > 系统相关 >【C++】c++单继承、多继承、菱形继承内存布局(虚函数表结构)

【C++】c++单继承、多继承、菱形继承内存布局(虚函数表结构)

时间:2023-05-31 12:11:13浏览次数:69  
标签:虚表 函数 继承 void C++ int c++ cout


这里写图片描述


单继承:只有一个基类和一个派生类

class Base
{
public:
    virtual void fun1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void fun2()
    {
        cout << "Base::func2()" << endl;
    }
private:
    int b;
};
class Derive :public Base
{
public:
    virtual void fun1()           //重写基类虚函数,实现多态
    {
        cout << "Derive::func1()" << endl;
    }

    virtual void fun3()
    {
        cout << "Derive::func3()" << endl;
    }
    void fun4()
    {
        cout << "Derive::func4()" << endl;
    }
private:
    int d;
};

1. 虚表就是存放虚函数的表。

2. 主函数中分别定义一个基类对象和一个派生类对象,通过调试窗口可以看到所谓的虚表,如下图(整型b和d未初始化):

这里写图片描述

>也许你会有疑问:调试窗口中派生类虚表为什么看不到Derive中的fun3()函数,这是编译器的问题,我所用的是vs2013,在调试的时候确实不见fun3()函数,所以有时编译器的调试窗口显示的也不能完全相信,那有什么办法证明fun3()函数也在派生类虚表里呢?通过打印虚表!

代码如下:

typedef void (*FUNC)();        //重定义函数指针,指向函数的指针
void PrintVTable(int* vTable)  //打印虚函数表
{
    if (vTable == NULL)
    {
        return;
    }
    cout << "虚函数表地址:" << vTable << endl;
    int  i = 0;
    for (; vTable[i] != 0; ++i)
    {
        printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
        FUNC f = (FUNC)vTable[i];
        f();         //访问虚函数
    }
    cout << endl;
}
 
void Test1()
{
    Base b;
    Derive d;
    int* tmp = (int*)(*(int*)&b);     //取到虚函数的地址
    PrintVTable(tmp);
    int* tmp1 = (int*)(*(int*)&d);
    PrintVTable(tmp1);
}
 

解析:int* tmp = (int*)(*(int*)&b);

如下图:

这里写图片描述


打印虚表:

这里写图片描述


>注意:

不知你是否注意到派生类中还有一个函数:void fun4();
虚函数是为了实现动态多态,是当程序运行到该函数时才会去虚表里找这个函数;而函数的重载实现的是静态多态, 是在程序编译时就能找到该函数地址,而函数:void fun4();不是虚函数,自然不会在虚函数表里。


这里写图片描述


class Base1   //基类
{
public:
    virtual void fun1()
    {
        cout << "Base1::fun1" << endl;
    }
    virtual void fun2()
    {
        cout << "Base1::fun2" << endl;
    }
private:
    int b1;
};
class Base2  //基类
{
public:
    virtual void fun1()
    {
        cout << "Base2::fun1" << endl;
    }
    virtual void fun2()
    {
        cout << "Base2::fun2" << endl;
    }
private:
    int b2;
};
class Derive : public Base1, public Base2  //派生类
{
public:
    virtual void fun1()
    {
        cout << "Derive::fun1" << endl;
    }
    virtual void fun3()
    {
        cout << "Derive::fun3" << endl;
    }
private:
    int d1;
};
 

调试看结果:

这里写图片描述

同样以上面单继承打印虚表函数来打印多继承虚表:void PrintVTable(int* vTable); //打印虚函数表

void Test1()
{
    Derive d1;
    int* VTable = (int*)(*(int*)&d1);
    PrintVTable(VTable);
    VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));
    PrintVTable(VTable);
}
 

解析:VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));

这里写图片描述


打印多继承虚表如下图:

这里写图片描述


这里写图片描述


先了解什么是菱形继承?

这里写图片描述
这里写图片描述

下列代码是菱形继承体系:

class Base          //Derive的间接基类
{
public:
    virtual void func1()
    {
        cout << "Base::func1()" << endl;
    }
    virtual void func2()
    {
        cout << "Base::func2()" << endl;
    }
private:
    int b;
};
class Base1 :public Base  //Derive的直接基类
{
public:
    virtual void func1()          //重写Base的func1()
    {
        cout << "Base1::func1()" << endl;
    }
    virtual void func3()
    {
        cout << "Base1::func3()" << endl;
    }
private:
    int b1;
};
class Base2 :public Base    //Derive的直接基类
{
public:
    virtual void func1()       //重写Base的func1()
    {
        cout << "Base2::func2()" << endl;
    }
    virtual void func4()
    {
        cout << "Base2::func4()" << endl;
    }
private:
    int b2;
};
class Derive :public Base1, public Base2
{
public:
    virtual void func1()          //重写Base1的func1()
    {
        cout << "Derive::func1()" << endl;
    }
    virtual void func5()
    {
        cout << "Derive::func5()" << endl;
    }
private:
    int d;
};
 

菱形继承其实是一个单继承与多继承的结合。

这里写图片描述


下面跟踪Derive对象d的内存布局:

这里写图片描述

进一步解析如下图:

这里写图片描述


同样以上面单继承打印虚表函数来打印多继承虚表:void PrintVTable(int* vTable); //打印虚函数表

这里写图片描述


这里写图片描述


这里写图片描述


>1. 先来看下面两个方案:

这里写图片描述

> 主要解析第一种方案:

vs2003下虚继承的VBPTR及VBTBL:
在类中增加一个指针(VBPTR)指向一个VBTBL,这个VBTBL的第一项记载的是从VBPTR 与本类的偏移地址,如果本类有虚函数,那么第一项是FF FF FF FC(也就是-4),如果没有则是零,第二项起是VBPTR与本类的虚基类的偏移值。

下面这段代码与上面菱形继承(非虚继承)类似:

class Base
{
public:
    virtual void fun1()
    {
        cout << "Base::fun1()" << endl;
    }
    virtual void fun2()
    {
        cout << "Base::fun2()" << endl;
    }
private:
    int b;
};
class Base1 :virtual public Base  虚继承
{
public:
    virtual void fun1()          //重写Base的func1()
    {
        cout << "Base1::fun1()" << endl;
    }
    virtual void fun3()
    {
        cout << "Base1::fun3()" << endl;
    }
private:
    int b1;
};
class Base2 :virtual public Base  //虚继承
{
public:
    virtual void fun1()       //重写Base的func1()
    {
        cout << "Base2::fun1()" << endl;
    }
    virtual void fun4()
    {
        cout << "Base2::fun4()" << endl;
    }
private:
    int b2;
};
class Derive :public Base1, public Base2
{
public:
    virtual void fun1()          //重写Base1的func1()
    {
        cout << "Derive::fun1()" << endl;
    }
    virtual void fun5()
    {
        cout << "Derive::fun5()" << endl;
    }
private:
    int d;
};
 

1. 详细地分析一下vs2003下虚继承的VBPTR及VBTBL:

- 以Base1 b1;为例子,详细分析内存布局如下(Base2和Base1的内存布局相似):

sizeof(Base1) = 20;(下图中黑色区域中所有变量所占的大小)

这里写图片描述


当在主函数中定义两个对象Base1 b1和Base2 b2时,还可通过调试进一步探索其内存布局如下:

这里写图片描述



最后,我们再来探索一下 Derive d 的内存布局,首先我们先通过调试窗口来跟踪如下:

这里写图片描述


通过上面调试窗口可能没办法了解全部,那么请看下图所示:

sizeof(Derive) = 36;(下图黑色区域所有变量的大小)

这里写图片描述

我们怎么验证菱形继承(虚继承)的内存布局就是这样的呢?我们可以通过打印虚表!!

typedef void(*FUNC)();
void PrintVPTR(int* VPTR)                   //打印虚表(虚函数)
{
    cout << "虚函数表地址:" << VPTR << endl;
    for (int i = 0; VPTR[i] != 0; ++i)
    {
        printf("第%d个虚函数地址:0X%x->", i, VPTR[i]);
        FUNC f = (FUNC)VPTR[i];
        f();
    }
    cout << endl;
}
void PrintVBPTR(int* VBPTR)                  //打印偏移地址与值
{
    cout << "虚函数表地址:" << VBPTR << endl;
    int i = 0;
    printf("与本类的偏移地址:0X%x\n", VBPTR[i]);
    for (i = 1; VBPTR[i] != 0; i++)
    {
        cout << VBPTR[i] << " " << endl;
    }
    cout << endl;
}
 

主函数中的调用如下:

void Test1()
{
    Base b;
    Base1 b1;
    Base2 b2;
    Derive d;
    cout << "sizeof_Base = " << sizeof(Base) << endl;
    int* BvTable = (int*)(*((int*)&b));
    PrintVPTR(BvTable);
    cout << "-------------------------------" << endl;

    cout << "sizeof_Base1 = " << sizeof(Base1) << endl;
    int* BVPTR1 = (int*)(*((int*)&b1));                             //存放自己的虚函数(虚表)
    PrintVPTR(BVPTR1);
    int* VBPTR1 = (int*)(*((int*)&b1 + 1));//访问偏移地址以及偏移量
    PrintVBPTR(VBPTR1);
    int* VPTR1 = (int*)(*((int*)&b1 + (*(VBPTR1 + 1)) / 4 + 1));    //在Base1中访问Base虚表
    PrintVPTR(VPTR1);
    cout << "-------------------------------" << endl;

    cout << "sizeof_Base2 = " << sizeof(Base2) << endl;
    int* BVPTR2 = (int*)(*((int*)&b2));                      //存放自己的虚函数(虚表)
    PrintVPTR(BVPTR2);
    int* VBPTR2 = (int*)(*((int*)&b2 + 1));//访问偏移地址以及偏移量
    PrintVBPTR(VBPTR2);
    int* VPTR2 = (int*)(*((int*)&b2 + (*(VBPTR2 + 1)) / 4 + 1));//在Base2中访问Base虚表
    PrintVPTR(VPTR2);
    cout << "-------------------------------" << endl;

    cout << "sizeof_Derive = " << sizeof(Derive) << endl;
    int* dVPTR1 = (int*)(*((int*)&d));                           //存放自己的虚函数(虚表)
    PrintVPTR(dVPTR1);
    int* dVBPTR3 = (int*)(*((int*)&d + 1));//访问偏移地址以及偏移量
    PrintVBPTR(dVBPTR3);
    int* dVPTR2 = (int*)(*((int*)&d + 3));                       //在Derive中访问Base2虚表
    PrintVPTR(dVPTR2);
    int* dVBPTR = (int*)(*((int*)&d + 4));//访问偏移地址以及偏移量
    PrintVBPTR(dVBPTR);
    int* VPTR = (int*)(*((int*)&d + (*(dVBPTR3 + 1)) / 4 + 1));  //在Derive中访问Base虚表
    PrintVPTR(VPTR);
}
 

总结:

  1. 虚基类实例地址 = 派生类虚函数指针+派生类虚函数指针到虚基类实例地址的偏移量
  2. 可以通过虚拟继承消除二义性,但是虚拟继承的开销是增加虚函数指针。
 

标签:虚表,函数,继承,void,C++,int,c++,cout
From: https://www.cnblogs.com/tomato-haha/p/17445753.html

相关文章

  • C++ 在函数内部输出当前类名方式
    开发环境:QtCreator C++1usingnamespacestd;23/*基类汽车*/4classCar5{6public:7Car(){}8virtual~Car(){}9virtualvoidmove(void);10};1112/*基本属性汽车运动*/13voidCar::move(void)14{15cout<<......
  • C++多态虚函数表详解(多重继承、多继承情况)
    本文关键词:C++多态多继承多重继承虚函数表虚函数指针动态绑定概述:C++相对其他面向对象语言来说,之所以灵活、高效。很大程度的占比在于其多态技术和模板技术。C++虚函数表是支撑C++多态的重要技术,它是C++动态绑定技术的核心。本文章将着重图解虚函数表相关知识,在阅读本文......
  • c++中的析构函数和纯虚函数
    析构函数:c++中当delete一个类对象时,会默认调用其析构函数,析构函数的作用就是释放对象占用的堆空间。一般基类的析构函数需写成虚函数,这是因为在多态下,我们一般用基类的指针来指向一个子类对象,若基类的虚函数未被重写,那么可能会造成内存泄漏。因此需要在子类重写基类的虚函数来......
  • 【c&c++】erase怎么用c语言,C++ erase()函数使用时的注意点
    遇见的场景删除vector容器指定元素时;erase()函数的用法vector::erase():从指定容器删除指定位置的元素或某段范围内的元素。具体用法如下:iteratorerase(iterator_Where);删除指定位置的元素,返回值是一个迭代器,指向删除元素的下一个元素;iteratorerase(iterator_First,i......
  • 继承中构造方法案例
    /***创建一个教师类,有姓名和年龄两个参数*打印出比如“姓名为张三年龄30岁的老师正在讲课”*创建一个学生类,有姓名,年龄,成绩三个参数*打印出比如“姓名为李四年龄20岁成绩100分的学生正在上课”*///测试类publicclasstest1{publicstaticvoidmain(String[......
  • BDB c++例子,从源码编译到运行
    第一步先下载源码,解压后./dist/configure--enable-cxx编译,然后make,makeinstall--enable-cxxTobuildtheBerkeleyDBC++API,enter--enable-cxxasanargumenttoconfigure. 默认的安装路径是:/usr/local/BerkeleyDB.6.1/ 代码如下:#include<stdlib.h>#include<strin......
  • MongoDB C++ gridfs worked example
    使用libmongoc,参考:http://mongoc.org/libmongoc/current/mongoc_gridfs_t.html#include<mongoc.h>#include<stdio.h>#include<stdlib.h>#include<fcntl.h>classMongoGridFS{public:MongoGridFS(constchar*db);~MongoGridFS();......
  • mongodb c++ driver安装踩坑记
     安装教程:https://mongodb.github.io/mongo-cxx-driver/mongocxx-v3/installation/(1)“initializer_list”filenotfoundhttp://stackoverflow.com/questions/19493671/initializer-list-no-such-file-or-directorySinceyouareusing GCC-4.8 andyourproblemisthatyoud......
  • 单链表(c++实现)
    template<typenameT>classListNode{public:explicitListNode(Tvalue_,ListNode*next_=nullptr):value(value_),next(next_){}TgetValue()const{returnvalue;}ListNode<T>*getNext()const{returnnext;};voidsetNext(ListNo......
  • 内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?
    一、c/c++程序内存区域划分c和c++的内存区域划分是十分相似的,因为c++是完全兼容c语言,是c语言的面向对象的升级版。接下来看如下图:程序的内存区域被划分成6个区域。内核空间、栈、内存映射段、堆、数据段、代码段。下面是对相关内存区域名词解释:栈又叫堆栈--非静态局部变量/函数参数......