C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现(四)
面一次的图来说还要简单啦! Base1已经没有虚函数表了! (真实情况并非完全这样, 请继续往下看!)
? ? 现在的大小及偏移情况: 注意: ?sizeof(Base1) == 8;?
? ? ? ? ?
? ?重点是看虚函数的位置, 进入函数调用(和前一次是一样的):
?
1 Derive1 d1;
2 Derive1* pd1 = &d1;
3 pd1->derive1_fun2();
?
?
? ? 反汇编调用代码:
?
复制代码
1 ? ? pd1->derive1_fun2();
2 012E4BA6 ?mov ? ? ? ? eax,dword ptr [pd1] ?
3 012E4BA9 ?mov ? ? ? ? edx,dword ptr [eax] ?
4 012E4BAB ?mov ? ? ? ? esi,esp ?
5 012E4BAD ?mov ? ? ? ? ecx,dword ptr [pd1] ?
6 012E4BB0 ?mov ? ? ? ? eax,dword ptr [edx+0Ch] ?
7 012E4BB3 ?call ? ? ? ?eax?
复制代码
?
?
? ? ?这段汇编代码和前面一个完全一样!, 那么问题就来了! Base1 已经没有虚函数表了, 为什么还是把b1的第1个元素当作__vfptr呢?
? ? ?不难猜测: 当前的布局已经发生了变化, 有虚函数表的基类放在对象内存前面!? , 不过事实是否属实? 需要仔细斟酌.
?
? ? ?我们可以通过对基类成员变量求偏移来观察:
? ? ? ? ? ? ?
?
? ? 可以看到:?
? ? ? ? ? ? ? &d1==0x~d4
? ? ? ? ? ? ? &d1.Base1::__vfptr==0x~d4
? ? ? ? ? ? ? &d1.base2_1==0x~d8
? ? ? ? ? ? ? &d1.base2_2==0x~dc
? ? ? ? ? ? ? &d1.base1_1==0x~e0
? ? ? ? ? ? ? &d1.base1_2==0x~e4
?
? ? 所以不难验证: 我们前面的推断是正确的, 谁有虚函数表, 谁就放在前面!
?
? ? 现在类的布局情况:
? ? ? ? ??
?
?
?
? ? 那么, 如果两个基类都没有虚函数表呢?
?
? ? 10. What if 两个基类都没有虚函数表
?
? ? ?代码如下:
?
复制代码
?1 class Base1
?2 {
?3 public:
?4 ? ? int base1_1;
?5 ? ? int base1_2;
?6 };
?7?
?8 class Base2
?9 {
10 public:
11 ? ? int base2_1;
12 ? ? int base2_2;
13 };
14?
15 // 多继承
16 class Derive1 : public Base1, public Base2
17 {
18 public:
19 ? ? int derive1_1;
20 ? ? int derive1_2;
21?
22 ? ? // 自身定义的虚函数
23 ? ? virtual void derive1_fun1() {}
24 ? ? virtual void derive1_fun2() {}
25 };
复制代码
?
?
? ? ?前面吃了个亏, 现在先来看看VS的基本布局:
? ? ? ? ? ??
?
? ? ? ? ? ?可以看到, 现在__vfptr已经独立出来了, 不再属于Base1和Base2!
?
? ?看看求偏移情况: ?
? ? ? ? ? ?
?
? ?Ok, 问题解决! 注意高亮的那两行, &d1==&d1.__vfptr, 说明虚函数始终在最前面!
?
? ? 不用再废话, 相信大家对这种情况已经有底了.
?
? ? 对象布局:
? ? ? ??
?
? ? 11. 如果有三个基类: 虚函数表分别是有, 没有, 有!
?
? ? ? ? ?这种情况其实已经无需再讨论了, 作为一个完结篇....
?
? ? ? ? ?上代码:
?
复制代码
?1 class Base1
?2 {
?3 public:
?4 ? ? int base1_1;
?5 ? ? int base1_2;
?6?
?7 ? ? virtual void base1_fun1() {}
?8 ? ? virtual void base1_fun2() {}
?9 };
10?
11 class Base2
12 {
13 public:
14 ? ? int base2_1;
15 ? ? int base2_2;
16 };
17?
18 class Base3
19 {
20 public:
21 ? ? int base3_1;
22 ? ? int base3_2;
23?
24 ? ? virtual void base3_fun1() {}
25 ? ? virtual void base3_fun2() {}
26 };
27?
28 // 多继承
29 class Derive1 : public Base1, public Base2, public Base3
30 {
31 public:
32 ? ? int derive1_1;
33 ? ? int derive1_2;
34?
35 ? ? // 自身定义的虚函数
36 ? ? virtual void derive1_fun1() {}
37 ? ? virtual void derive1_fun2() {}
38 };
复制代码
?
?
? ? 只需要看看偏移就行了:
? ? ? ? ??
?
? ? 只需知道: 谁有虚函数表, 谁就往前靠!
?
C++中父子对象指针间的转换与函数调用
?
? ? ? 讲了那么多布局方面的东东, 终于到了发声, 好累呀!!!
?
? ? ? 通过前面的讲解内容, 大家至少应该明白了各类情况下类对象的内存布局了. 如果还不会.....呃..... !@#$%^&*
?
? ? ? 进入正题~
?
? ? ? 由于继承完全拥有父类的所有, 包括数据成员与虚函数表, 所以:
? ? ? ? ? ? ? 把一个继承类强制转换为一个基类是完全可行的.
?
? ? ?如果有一个Derive1的指针, 那么:
? ? ? ? ? ? ? 得到Base1的指针: ?Base1* pb1 = pd1;?
?
? ? ? ? ? ? ? 得到Base2的指针: ?Base2* pb2 = pd1;?
?
? ? ? ? ? ? ? 得到Base3的指针: ?Base3* pb3 = pd1;?
?
? ? ?非常值得注意的是:
? ? ? ? ? ? 这是在基类与继承类之间的转换, 这种转换会自动计算偏移! 按照前面的布局方式!
? ? ? ? ? ? 也就是说: 在这里极有可能: pb1 != pb2 != pb3 ?~~, 不要以为她们都等于 pd1!
?
?
?
? ? ?至于函数调用, 我想, 不用说大家应该知道了:
? ? ? ? ? ?1. 如果不是虚函数, 直接调用指针对应的基本类的那个函数
? ? ? ? ? ?2. 如果是虚函数, 则查找虚函数表, 并进行后续的调用.
? ? ? ? ? ? ? ? ? ? ?虚函数表在定义一个时, 编译器就为我们创建好了的. 所有的, 同一个类, 共