探索c++的对象模型(一):单继承和多继承的对象模型

虚函数表就是通过一块连续内存来保存虚函数的地址

单继承虚函数的对象模型

class A
{
public:
     virtual void func1()
     {
           printf("A::func1\n");
     }
     virtual void func2()
     {
           printf("A::func2\n");
     }
public:
     int _a;
};
class B
{
public:
     virtual void func1()
     {
           printf("B::func1\n");
     }
     virtual void func3()
     {
           printf("B::func3\n");
     }
     virtual void func4()
     {
           printf("B::func4\n");
     }
public:
     int _b;
};
typedef void(*FUNC) ();
void PrintVTable(int *VTable)
{
     size_t i = 0;
     for (; VTable[i] != NULL; i++)
     {
           printf("第%d个虚函数:0x%p---->", i, VTable[i]);
           //将取出来的地址强转为函数指针调用函数
           FUNC f = (FUNC)VTable[i];
           f();
           printf("\n");
     }
}
int main()
{
     A a;
     B b;
     //打印两个类的虚表
     PrintVTable((int *)*((int *)(&a)));
     PrintVTable((int *)*((int *)(&b)));
     system("pause");
     return 0;
}

因为虚表里存的是一个一个的函数指针,而虚标的地址又存在类的最上面的四个字节,所以呀只要我们将最上面的四个字节里的地址取出来然后去根据这个地址来找到虚表,然后我门可以将虚表看做一个数组,数组里的每一个元素都是地址(函数指针),那么我们将这个数组打印即可


那么这个打印虚表的难点为这句
 PrintVTable((int *)*((int *)(&a)))
此句就时将类的地址取出来然后强转为(int*),只将头上4个字节取出,在解引用,但是解引用了之后的值是一个int型的数据,那我们再将它强转为(int *)然后根据地址找虚表即可

那么我们可以发现此代码是在32位下运行的,那么我们怎么写一份代码既可以在32位下运行也可以在64位运行呢
 PrintVTable((int **)*((int **)(&a)))
int**解引用了之后,在多少位下指针就是多少位


我们来一下内存和打印的对应
我们可以看到在原来A类的func1函数的地方B进行了虚函数的重写
探索c++的对象模型(一):单继承和多继承的对象模型

我门并没有看到func3和func4 在虚表中这个编译器的一个bug,但其实它是真实存在的,通过打印我们也能看出来

我们再来手动的梳理一下来画张图
探索c++的对象模型(一):单继承和多继承的对象模型

多继承虚函数的对象模型

class A
{
public:
     virtual void func1()
     {
           printf("A::func1\n");
     }
     virtual void func2()
     {
           printf("A::func2\n");
     }
public:
     int _a;
};
class B
{
public:
     virtual void func1()
     {
           printf("B::func1\n");
     }
     virtual void func2()
     {
           printf("B::func2\n");
     }
public:
     int _b;
};
class C :public A,public B
{
     virtual void func1()
     {
           printf("C::func1\n");
     }
     virtual void func3()
     {
           printf("C::func3\n");
     }
public:
     int _c;
};
typedef void(*FUNC) ();
void PrintVTable(int *VTable)
{
     size_t i = 0;
     printf("虚表地址:%p\n", VTable);
     for (; VTable[i] != NULL; i++)
     {
           printf("第%d个虚函数:0x%p---->", i, VTable[i]);
           //将取出来的地址强转为函数指针调用函数
           FUNC f = (FUNC)VTable[i];
           f();
     }
     printf("\n\n");
}
int main()
{
     C c;
     //打印两个类的虚表
     PrintVTable((int *)*((int *)(&c)));
     PrintVTable((int *)*((int *)(&c) + sizeof(A) / 4));
     system("pause");
     return 0;
}
然后我们来看一下多继承的模型
探索c++的对象模型(一):单继承和多继承的对象模型
以下几点需注意
  1. 当子类里有对两个父类都重写的虚函数时,都会进行覆盖重写
  2. 子类会继承父类的虚表
  3. 子类的没有重写的函数放在(先继承哪个父类)就放在那个父类的虚表中,(以上代码子类先继承了A,所以子类里其他的函数都放在A的虚表里)