【虚函数、虚拟指针、虚表指针】解析多重继承时,虚表的分布

来源:互联网 发布:免费的大数据分析平台 编辑:程序博客网 时间:2024/06/06 13:18

这里写图片描述

#include<iostream>using namespace std;//抽象基类:动物类class CAnimal{public:    virtual void EatFood(string strSomething) = 0;    virtual void Drink(string strSomething) = 0;};//CAnimal的派生类:CMammalclass CMammal : public  CAnimal{public:    virtual void EatFood(string strSomething)    {        cout << "CMammal::EatFood()" << endl;    }    virtual void Drink(string strSomething)    {        cout << "CMammal::Drink()" << endl;    }};//CAnimal的继承类:CAfricaAnimal非洲动物class CAfricaAnimal : public CAnimal{public:    virtual void EatFood(string strSomething)    {        cout << "CAfricaAnimal::EatFood()" << endl;    }    virtual void Drink(string strSomething)    {        cout << "CAfricaAnimal::Drink()" << endl;    }};//同时继承自CMammal,CAfricaAnimal:CLion狮子类class CLion : public CMammal, public CAfricaAnimal{public:    virtual void RunCrazily()    {        cout << "CLion::RunCrazily()" << endl;    }};

现有以下

int main(){    CLion sinba;    return 0;}

通过在return 0;语句前添加一个断点,查看sinba的值可以得到如下结果:
这里写图片描述

由此我们可以得出如下结论:

在多重继承中,会根据含有虚函数的基类确定有多少张虚表。有多少个含有虚函数的基类,就会有多少张虚表每个基类的虚表是按照在继承列表中的声明顺序而存放的。从上面的调试窗口可以看到,Clion中有两张虚表:首先是Cmammal的,然后是CafricaAnimal的。

在Clion中定义了一个Clion自己的虚函数RunCrazily,可是从调试窗口中并没有看到RunCrazily。那么这张表到底存放在哪里呢?通过网上查阅资料,得知这张表其实是存放在第一张虚表中的。一下是测试代码:

CLion sinba;typedef void (*FUN)();   //函数指针FUN run_crazily = (FUN)(*((int*)*(int*)(&sinba)+2)); run_crazily();//CLion::RunCrazily()

这里写图片描述

关于上面复杂指针转换的分析:  (int*)(&sinba)得到的是一个指向第一张虚表地址的指针,该虚表中存放的是虚函数的函数指针,每项占4字节。对该地址取内容:*(int*)(&sinba) 可以得到第一张虚表的首地址,但应该将该值转换成int*,使得每次指针+1操作都内存向前移动4字节,即虚表中一个元素的长度:(int*)*(int*)(&sinba)。因为虚表中元素占4字节,现在把这个指针向前移动两个元素: (int*)*(int*)(&sinba)+2,得到表中第三项的地址,对该项取内容,得到函数的入口地址,即CLion::RunCrazily()的入口地址:*((int*)*(int*)(&sinba)+2). 得到这个值以后还不能直接调用,应该将该函数入口点转换成相应的函数原型(FUN)(*((int*)*(int*)(&sinba)+2))。现在可以调用了如果要得到第二张虚表中的某项函数可以按照上面分析的方法调用到:(int*)(&sinba):一个指向第一张虚表地址的指针(这里必须转换成int*,方便在指针移动的时候可以向前移动4字节,也就是一个虚表指针的长度)(int*)(&sinba)+1:一个指向第二张虚表地址的指针*((int*)(&sinba)+1):对上面的地址取内容,得到第二张虚表的首地址(int*)*((int*)(&sinba)+1)+1:第二张虚表中第二项的地址(这里也必须转换成int*再+1,原因同上面的)*((int*)*((int*)(&sinba)+1)+1):对上面的地址取内容,得到第二张虚表中第二项的入口地址(FUN)(*((int*)*((int*)(&sinba)+1)+1)):对上面的地址强制转换成相应原型的函数指针
1 0
原创粉丝点击