前面的从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的要么是单一类,要么是普通的多重继承,没有讨论虚拟继承,下面就来看一看,当引入虚拟继承之后,成员函数指针会有什么变化。
下面来看c++源码:
#include <cstdio> using namespace std;class Top { public:virtual int get1() {return 1;}virtual int get2() {return 2;} };class Left : virtual public Top { public:virtual int get3() {return 3;}virtual int get4() {return 4;} };class Right : virtual public Top { public:virtual int get5() {return 5;}virtual int get6() {return 6;} };class Bottom : public Left, public Right { public:virtual int get1() {return 7;}virtual int get3() {return 8;}virtual int get5() {return 9;}virtual int get7() {return 10;} };int main() {Bottom b;Bottom* bp = &b;Left* lp = bp;Right* rp = bp;Top* tp = bp;/******************************定义各类指针函数**********************/int(Top::*tgp1)() = &Top::get1;int(Top::*tgp2)() = &Top::get2;int(Left::*lgp1)() = &Left::get1;int(Left::*lgp2)() = &Left::get2;int(Left::*lgp3)() = &Left::get3;int(Left::*lgp4)() = &Left::get4;int(Right::*rgp1)() = &Right::get1;int(Right::*rgp2)() = &Right::get2;int(Right::*rgp5)() = &Right::get5;int(Right::*rgp6)() = &Right::get6;int(Bottom::*bgp1)() = &Bottom::get1;int(Bottom::*bgp2)() = &Bottom::get2;int(Bottom::*bgp3)() = &Bottom::get3;int(Bottom::*bgp4)() = &Bottom::get4;int(Bottom::*bgp5)() = &Bottom::get5;int(Bottom::*bgp6)() = &Bottom::get6;int(Bottom::*bgp7)() = &Bottom::get7;/****************************输出各类成员函数指针的大小*******************/printf("sizeof(tgp1) = %d\n", sizeof(tgp1));printf("sizeof(tgp2) = %d\n", sizeof(tgp2));printf("\n");printf("sizeof(lgp1) = %d\n", sizeof(lgp1));printf("sizeof(lgp2) = %d\n", sizeof(lgp2));printf("sizeof(lgp3) = %d\n", sizeof(lgp3));printf("sizeof(lgp4) = %d\n", sizeof(lgp4));printf("\n");printf("sizeof(rgp1) = %d\n", sizeof(rgp1));printf("sizeof(rgp2) = %d\n", sizeof(rgp2));printf("sizeof(rgp5) = %d\n", sizeof(rgp5));printf("sizeof(rgp6) = %d\n", sizeof(rgp6));printf("\n");printf("sizeof(bgp1) = %d\n", sizeof(bgp1));printf("sizeof(bgp2) = %d\n", sizeof(bgp2));printf("sizeof(bgp3) = %d\n", sizeof(bgp3));printf("sizeof(bgp4) = %d\n", sizeof(bgp4));printf("sizeof(bgp5) = %d\n", sizeof(bgp5));printf("sizeof(bgp6) = %d\n", sizeof(bgp6));printf("sizeof(bgp7) = %d\n", sizeof(bgp7));printf("\n");/*********************************输出各类成员函数指针*****************/printf("&Top::get1 = %lu\n", &Top::get1);printf("&Top::get2 = %lu\n", &Top::get2);printf("&Left::get1 = %lu\n", &Left::get1);printf("&Left::get2 = %lu\n", &Left::get2);printf("&Left::get3 = %lu\n", &Left::get3);printf("&Left::get4 = %lu\n", &Left::get4);printf("&Right::get1 = %lu\n", &Right::get1);printf("&Right::get2 = %lu\n", &Right::get2);printf("&Right::get5 = %lu\n", &Right::get5);printf("&Right::get6 = %lu\n", &Right::get6);printf("&Bottom::get1 = %lu\n", &Bottom::get1);printf("&Bottom::get2 = %lu\n", &Bottom::get2);printf("&Bottom::get3 = %lu\n", &Bottom::get3);printf("&Bottom::get4 = %lu\n", &Bottom::get4);printf("&Bottom::get5 = %lu\n", &Bottom::get5);printf("&Bottom::get6 = %lu\n", &Bottom::get6);printf("&Bottom::get7 = %lu\n", &Bottom::get7);/********************************用成员指针调用各类成员函数*****************/(bp->*bgp1)();(bp->*bgp2)();(bp->*bgp3)();(bp->*bgp4)();(bp->*bgp5)();(bp->*bgp6)();(bp->*bgp7)();}
下面是输出结果:
通过上面的输出,我们可以得到2个信息:
1 当类的继承层次中包含虚基类时,成员函数指针编程了12字节
2 程序中定义了17个成员函数指针,但是只保存了3种不同的地址
对于第2点,通过从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)可以知道,由于所有的成员函数在相应的虚表中相对于虚表首地址只有3中偏移量,0 4 8,因此,只要3个vcall函数就够了,而且vcall函数的功能也一样,通过寄存器ecx传递进来的this指针,得到成员函数所在的正确虚表首地址,然后从虚表中获取成员函数地址,并跳转到该地址处执行。下面是3个vcall函数汇编码:
??_9Top@@$BA@AE PROC ; Top::`vcall'{0}', COMDATmov eax, DWORD PTR [ecx];寄存器ecx保存有this指针,这里将this指针所指向的内存内容(即vftable首地址);给寄存器eaxjmp DWORD PTR [eax];取eax保存的内存地址里面的内容(即虚表首地址处存储的虚函数地址),然后跳转到该地址执行 ??_9Top@@$B3AE PROC ; Top::`vcall'{4}', COMDATmov eax, DWORD PTR [ecx];寄存器ecx保存有this指针,这里将this指针所指向的内存内容(即vftable首地址);给寄存器eaxjmp DWORD PTR [eax+4];取偏移eax保存的内存地址4byte处的内存内容(即偏移虚表首地址4byte处内存存储的虚函数地址),然后跳转到该地址执行 ??_9Bottom@@$B7AE PROC ; Bottom::`vcall'{8}', COMDATmov eax, DWORD PTR [ecx];寄存器ecx保存有this指针,这里将this指针所指向的内存内容(即vftable首地址);给寄存器eaxjmp DWORD PTR [eax+8];取偏移eax保存的内存地址8byte处的内存内容(即偏移虚表首地址8byte处内存存储的虚函数地址),然后跳转到该地址执行
下面是类Bottom的继承关系图(菱形继承)
下面是各个类的类存布局:
下面就来看看引入虚拟继承之后,成员函数指针存储的是什么值。
下面来看定义这些成员指针变量的汇编码:
60 : int(Top::*tgp1)() = &Top::get1; mov DWORD PTR _tgp1$[ebp], OFFSET ??_9Top@@$BA@AE ; `vcall'{0}'将??_9Top@@$BA@AE所代表的内存地址给tgp1 即存储vcall{0}的地址; 61 : int(Top::*tgp2)() = &Top::get2;mov DWORD PTR _tgp2$[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}' 同定义tgp1; 62 : int(Left::*lgp1)() = &Left::get1;mov DWORD PTR $T4213[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}';将OFFSET ??_9Top@@$BA@AE所代表的的内存地址(vcall{0}地址)给临时对象ST4213首地址处内存mov DWORD PTR $T4213[ebp+4], 0;将0给偏移临时对象ST4213首地址4byte处内存mov DWORD PTR $T4213[ebp+8], 4;将4给偏移临时对象ST4213首地址8byte处内存mov ecx, DWORD PTR $T4213[ebp];将临时对象ST4213首地址处内存内容给寄存器ecxmov DWORD PTR _lgp1$[ebp], ecx;将寄存器ecx的值给lgp1首地址处内存mov edx, DWORD PTR $T4213[ebp+4];将偏移临时对象ST4213首地址4byte处内存内容给寄存器edxmov DWORD PTR _lgp1$[ebp+4], edx;将edx的值给偏移lgp1首地址4byte处内存mov eax, DWORD PTR $T4213[ebp+8];将偏移临时对象ST4213首地址8byte处内存内容给寄存器eaxmov DWORD PTR _lgp1$[ebp+8], eax;将eax的值给偏移lgp1首地址8byte处内存; 63 : int(Left::*lgp2)() = &Left::get2;;同定义lgp1mov DWORD PTR $T4214[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'mov DWORD PTR $T4214[ebp+4], 0mov DWORD PTR $T4214[ebp+8], 4mov ecx, DWORD PTR $T4214[ebp]mov DWORD PTR _lgp2$[ebp], ecxmov edx, DWORD PTR $T4214[ebp+4]mov DWORD PTR _lgp2$[ebp+4], edxmov eax, DWORD PTR $T4214[ebp+8]mov DWORD PTR _lgp2$[ebp+8], eax; 64 : int(Left::*lgp3)() = &Left::get3;;同定义lgp1mov DWORD PTR $T4215[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'mov DWORD PTR $T4215[ebp+4], 0mov DWORD PTR $T4215[ebp+8], 0mov ecx, DWORD PTR $T4215[ebp]mov DWORD PTR _lgp3$[ebp], ecxmov edx, DWORD PTR $T4215[ebp+4]mov DWORD PTR _lgp3$[ebp+4], edxmov eax, DWORD PTR $T4215[ebp+8]mov DWORD PTR _lgp3$[ebp+8], eax; 65 : int(Left::*lgp4)() = &Left::get4;;同定义lgp1mov DWORD PTR $T4216[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'mov DWORD PTR $T4216[ebp+4], 0mov DWORD PTR $T4216[ebp+8], 0mov ecx, DWORD PTR $T4216[ebp]mov DWORD PTR _lgp4$[ebp], ecxmov edx, DWORD PTR $T4216[ebp+4]mov DWORD PTR _lgp4$[ebp+4], edxmov eax, DWORD PTR $T4216[ebp+8]mov DWORD PTR _lgp4$[ebp+8], eax; 66 : int(Right::*rgp1)() = &Right::get1;;同定义lgp1mov DWORD PTR $T4217[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'mov DWORD PTR $T4217[ebp+4], 0mov DWORD PTR $T4217[ebp+8], 4mov ecx, DWORD PTR $T4217[ebp]mov DWORD PTR _rgp1$[ebp], ecxmov edx, DWORD PTR $T4217[ebp+4]mov DWORD PTR _rgp1$[ebp+4], edxmov eax, DWORD PTR $T4217[ebp+8]mov DWORD PTR _rgp1$[ebp+8], eax; 67 : int(Right::*rgp2)() = &Right::get2;;同定义lgp1mov DWORD PTR $T4218[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'mov DWORD PTR $T4218[ebp+4], 0mov DWORD PTR $T4218[ebp+8], 4mov ecx, DWORD PTR $T4218[ebp]mov DWORD PTR _rgp2$[ebp], ecxmov edx, DWORD PTR $T4218[ebp+4]mov DWORD PTR _rgp2$[ebp+4], edxmov eax, DWORD PTR $T4218[ebp+8]mov DWORD PTR _rgp2$[ebp+8], eax; 68 : int(Right::*rgp5)() = &Right::get5;;同定义lgp1mov DWORD PTR $T4219[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'mov DWORD PTR $T4219[ebp+4], 0mov DWORD PTR $T4219[ebp+8], 0mov ecx, DWORD PTR $T4219[ebp]mov DWORD PTR _rgp5$[ebp], ecxmov edx, DWORD PTR $T4219[ebp+4]mov DWORD PTR _rgp5$[ebp+4], edxmov eax, DWORD PTR $T4219[ebp+8]mov DWORD PTR _rgp5$[ebp+8], eax; 69 : int(Right::*rgp6)() = &Right::get6;;同定义lgp1mov DWORD PTR $T4220[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'mov DWORD PTR $T4220[ebp+4], 0mov DWORD PTR $T4220[ebp+8], 0mov ecx, DWORD PTR $T4220[ebp]mov DWORD PTR _rgp6$[ebp], ecxmov edx, DWORD PTR $T4220[ebp+4]mov DWORD PTR _rgp6$[ebp+4], edxmov eax, DWORD PTR $T4220[ebp+8]mov DWORD PTR _rgp6$[ebp+8], eax; 70 : int(Bottom::*bgp1)() = &Bottom::get1;;同定义lgp1mov DWORD PTR $T4221[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'mov DWORD PTR $T4221[ebp+4], 0mov DWORD PTR $T4221[ebp+8], 4mov ecx, DWORD PTR $T4221[ebp]mov DWORD PTR _bgp1$[ebp], ecxmov edx, DWORD PTR $T4221[ebp+4]mov DWORD PTR _bgp1$[ebp+4], edxmov eax, DWORD PTR $T4221[ebp+8]mov DWORD PTR _bgp1$[ebp+8], eax; 71 : int(Bottom::*bgp2)() = &Bottom::get2;;同定义lgp1mov DWORD PTR $T4222[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'mov DWORD PTR $T4222[ebp+4], 0mov DWORD PTR $T4222[ebp+8], 4mov ecx, DWORD PTR $T4222[ebp]mov DWORD PTR _bgp2$[ebp], ecxmov edx, DWORD PTR $T4222[ebp+4]mov DWORD PTR _bgp2$[ebp+4], edxmov eax, DWORD PTR $T4222[ebp+8]mov DWORD PTR _bgp2$[ebp+8], eax; 72 : int(Bottom::*bgp3)() = &Bottom::get3;;同定义lg1mov DWORD PTR $T4223[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'mov DWORD PTR $T4223[ebp+4], 0mov DWORD PTR $T4223[ebp+8], 0mov ecx, DWORD PTR $T4223[ebp]mov DWORD PTR _bgp3$[ebp], ecxmov edx, DWORD PTR $T4223[ebp+4]mov DWORD PTR _bgp3$[ebp+4], edxmov eax, DWORD PTR $T4223[ebp+8]mov DWORD PTR _bgp3$[ebp+8], eax; 73 : int(Bottom::*bgp4)() = &Bottom::get4;;同定义lgp1mov DWORD PTR $T4224[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'mov DWORD PTR $T4224[ebp+4], 0mov DWORD PTR $T4224[ebp+8], 0mov ecx, DWORD PTR $T4224[ebp]mov DWORD PTR _bgp4$[ebp], ecxmov edx, DWORD PTR $T4224[ebp+4]mov DWORD PTR _bgp4$[ebp+4], edxmov eax, DWORD PTR $T4224[ebp+8]mov DWORD PTR _bgp4$[ebp+8], eax; 74 : int(Bottom::*bgp5)() = &Bottom::get5;;同定义lgp1mov DWORD PTR $T4225[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'mov DWORD PTR $T4225[ebp+4], 8mov DWORD PTR $T4225[ebp+8], 0mov ecx, DWORD PTR $T4225[ebp]mov DWORD PTR _bgp5$[ebp], ecxmov edx, DWORD PTR $T4225[ebp+4]mov DWORD PTR _bgp5$[ebp+4], edxmov eax, DWORD PTR $T4225[ebp+8]mov DWORD PTR _bgp5$[ebp+8], eax; 75 : int(Bottom::*bgp6)() = &Bottom::get6;;这里只给出程序要执行的汇编码的注释mov DWORD PTR $T4226[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}';将??_9Top@@$B3AE所代表的内存地址(即vcall{4}的地址)给临时对象ST4226的首地址处内存xor ecx, ecx;将ecx寄存器里面的值异或,这是不论ecx里面的值为什么,都为0jne SHORT $LN7@main;如果上面异或结果不为0,就跳转到标号$LN7@main处执行,否则,顺序执行,这里显然顺序执行mov DWORD PTR tv90[ebp], 8;将8给临时变量tv90jmp SHORT $LN8@main;跳转到标号$LN8@main处执行 $LN7@main:mov DWORD PTR tv90[ebp], 0 $LN8@main:mov edx, DWORD PTR tv90[ebp];将临时变量tv90的值给寄存器edxmov DWORD PTR $T4226[ebp+4], edx;i将edx的值给偏移临时对象ST4226首地址4byte处内存mov DWORD PTR $T4226[ebp+8], 0;将0给偏移临时对象ST4226首地址8byte处内存mov eax, DWORD PTR $T4226[ebp];将临时对象ST4226首地址处内存内容给寄存器eaxmov DWORD PTR _bgp6$[ebp], eax;将寄存器eax的内容给bgp6首地址处内存mov ecx, DWORD PTR $T4226[ebp+4];将偏移临时对象ST4226首地址4byte处内存内容给寄存器ecxmov DWORD PTR _bgp6$[ebp+4], ecx;将ecx的值给偏移bgp6首地址4byte处内存mov edx, DWORD PTR $T4226[ebp+8];将偏移临时对象ST4226首地址8byte处内存给寄存器edxmov DWORD PTR _bgp6$[ebp+8], edx;将edx的值给偏移bgp6首地址8byte处内存; 76 : int(Bottom::*bgp7)() = &Bottom::get7;;同定义lgp1mov DWORD PTR $T4229[ebp], OFFSET ??_9Bottom@@$B7AE ; Bottom::`vcall'{8}'mov DWORD PTR $T4229[ebp+4], 0mov DWORD PTR $T4229[ebp+8], 0mov eax, DWORD PTR $T4229[ebp]mov DWORD PTR _bgp7$[ebp], eaxmov ecx, DWORD PTR $T4229[ebp+4]mov DWORD PTR _bgp7$[ebp+4], ecxmov edx, DWORD PTR $T4229[ebp+8]mov DWORD PTR _bgp7$[ebp+8], edx
下面给出给成员函数指针保存的值的图示
成员函数指针保存的值清楚了,但是这些值又有什么意义呢?下面我们通过bgp1~bgp7成员函数指针来调用虚函数的汇编码来进行分析:
121 : (bp->*bgp1)(); mov edx, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器edxmov eax, DWORD PTR [edx+4];edx+4得到偏移对象b首地址4byte处内存地址,这里将该内存地址内容(vbtable首地址)给寄存器eaxmov ecx, DWORD PTR _bgp1$[ebp+8];将偏移对象bgp1首地址8byte处内存内容给寄存器ecxmov edx, DWORD PTR [eax+ecx];eax = vbtable首地址 ecx = 4 eax + ecx得到偏移vbtable首地址4byte处内存地址;这里取偏移vbtable首地址4byte处内存内容(即vbtable指针偏移虚基类Top子对象首地址的偏移量)给寄存器edxmov eax, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器eaxlea ecx, DWORD PTR [eax+edx+4];eax = 对象b首地址 edx = 即vbtable指针偏移虚基类Top子对象首地址的偏移量 eax + edx + 4 = 对象b中虚基类Top子对象的首地址add ecx, DWORD PTR _bgp1$[ebp+4];将偏移bgp1首地址4byte处内存内容(为0)与ecx里面的内容相加,得到对象b中虚基类Top子对象首地址;结果保存在寄存器ecx里面,将作为隐含参数传给相应的vcall函数call DWORD PTR _bgp1$[ebp];bgp1首地址处内存存有相应的vcall函数地址,这里调用相应的vcall函数;从这段汇编码中可以发现bgp1的第三项存储的是偏移vbtable首地址的偏移量,而第二项是;成员函数所在类相对于对象b中虚基类Top子对象首地址的偏移量; 122 : (bp->*bgp2)();;同bgp1的调用mov ecx, DWORD PTR _bp$[ebp]mov edx, DWORD PTR [ecx+4]mov eax, DWORD PTR _bgp2$[ebp+8]mov ecx, DWORD PTR [edx+eax]mov edx, DWORD PTR _bp$[ebp]lea ecx, DWORD PTR [edx+ecx+4]add ecx, DWORD PTR _bgp2$[ebp+4]call DWORD PTR _bgp2$[ebp]; 123 : (bp->*bgp3)();mov eax, DWORD PTR _bp$[ebp];将对象b的首地址给eax寄存器mov ecx, DWORD PTR [eax+4];eax = 对象b首地址 eax + 4 = 偏移对象b首地址4byte处内存地址;这里将偏移对象b对象首地址4byte内存地址内容(即vbtable首地址)给寄存器ecxmov edx, DWORD PTR _bgp3$[ebp+8];将偏移对象bgp3首地址8byte处内存内容(为0)给寄存器edxmov eax, DWORD PTR [ecx+edx];ecx = vbtable首地址 edx = 0 ecx + edx = vbtable首地址;这里取vbtable首地址处的内存内容(即vbtable指针偏移对象b首地址的偏移量,为-4)给寄存器eaxmov ecx, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器ecxlea ecx, DWORD PTR [ecx+eax+4];ecx = 对象b首地址 eax = -4 ecx + eax + 4 = ecx(即对象b首地址);这里将对象b首地址给寄存器ecxadd ecx, DWORD PTR _bgp3$[ebp+4];将偏移对象bgp3首地址4byte处内存内容(为0)与ecx相加 得到对象b首地址 结果保存在ecx寄存器中;ecx的值将作为隐含参数传递给相应的vcall函数call DWORD PTR _bgp3$[ebp];bgp3首地址处内存存有相应vcall函数地址,这里调用vcall函数;通过分析可知,bgp3第三项保存的仍然是偏移vbtable首地址的偏移量;第二项确实成员函数所在类偏移对象b的首地址的偏移量,与bgp1不同; 124 : (bp->*bgp4)();;同bgp3调用mov edx, DWORD PTR _bp$[ebp]mov eax, DWORD PTR [edx+4]mov ecx, DWORD PTR _bgp4$[ebp+8]mov edx, DWORD PTR [eax+ecx]mov eax, DWORD PTR _bp$[ebp]lea ecx, DWORD PTR [eax+edx+4]add ecx, DWORD PTR _bgp4$[ebp+4]call DWORD PTR _bgp4$[ebp]; 125 : (bp->*bgp5)();;同bgp3调用mov ecx, DWORD PTR _bp$[ebp]mov edx, DWORD PTR [ecx+4]mov eax, DWORD PTR _bgp5$[ebp+8]mov ecx, DWORD PTR [edx+eax]mov edx, DWORD PTR _bp$[ebp]lea ecx, DWORD PTR [edx+ecx+4]add ecx, DWORD PTR _bgp5$[ebp+4]call DWORD PTR _bgp5$[ebp]; 126 : (bp->*bgp6)();;同bgp3调用mov eax, DWORD PTR _bp$[ebp]mov ecx, DWORD PTR [eax+4]mov edx, DWORD PTR _bgp6$[ebp+8]mov eax, DWORD PTR [ecx+edx]mov ecx, DWORD PTR _bp$[ebp]lea ecx, DWORD PTR [ecx+eax+4]add ecx, DWORD PTR _bgp6$[ebp+4]call DWORD PTR _bgp6$[ebp]; 127 : (bp->*bgp7)();;同bgp3调用mov edx, DWORD PTR _bp$[ebp]mov eax, DWORD PTR [edx+4]mov ecx, DWORD PTR _bgp7$[ebp+8]mov edx, DWORD PTR [eax+ecx]mov eax, DWORD PTR _bp$[ebp]lea ecx, DWORD PTR [eax+edx+4]add ecx, DWORD PTR _bgp7$[ebp+4]call DWORD PTR _bgp7$[ebp]
通过上面的汇编码可以发现,包含虚拟继承的成员函数指针第一项保存的仍然是相应vcall函数的地址,第二项保存的是成员函数所属类相对于对象b首地址的偏移量(但是指向虚基类成员函数的指针有所不同,如bgp1和bgp2,他们保存的是成员函数所属类相对于虚基类Top首地址的偏移量),第三项保存的是相对于vbtable首地址的偏移量。
可以看到,与普通的多重继承相比,包含虚拟继承的成员函数指针还保留了有关vbtable的信息,这是因为,由于虚基类的特殊性(即它的位置在每次子类派生之后,都不一样,而非虚基类在子类中都有固定的偏移量),想要定位虚基类的首地址,必须通过vbtable。
同从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的一样,包含虚拟继承的时候,也可以将基类成员函数指针绑定到派生类对象或者派生类对象指针上,如可以(bp->*lgp1)(),编译器做内部转话(即将this指着从指向派生类对象首地址调整到指向派生类中基类子对象首地址处),但是不能将派生类成员函数指针绑定到基类对象或者基类对象指针上。
同从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的一样,包含虚拟继承的时候,也可以将基类成员成员指针转换为派生类成员函数指针,如可以bgp1 = lgp1,但是不能讲派生类成员函数指针转换为基类成员函数指针。