前面的从汇编看c++中成员函数指针(一)从汇编看c++成员函数指针(二)讨论的要么是单一类,要么是普通的多重继承,没有讨论虚拟继承,下面就来看一看,当引入虚拟继承之后,成员函数指针会有什么变化。

下面来看c++源码:

#include <cstdio>
using namespace std; class Top {
public:
virtual int get1() {
return ;
}
virtual int get2() {
return ;
}
}; class Left : virtual public Top {
public:
virtual int get3() {
return ;
}
virtual int get4() {
return ;
}
}; class Right : virtual public Top {
public:
virtual int get5() {
return ;
}
virtual int get6() {
return ;
}
}; class Bottom : public Left, public Right {
public:
virtual int get1() {
return ;
}
virtual int get3() {
return ;
}
virtual int get5() {
return ;
}
virtual int get7() {
return ;
}
}; 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}', COMDAT
mov eax, DWORD PTR [ecx];寄存器ecx保存有this指针,这里将this指针所指向的内存内容(即vftable首地址)
;给寄存器eax
jmp DWORD PTR [eax];取eax保存的内存地址里面的内容(即虚表首地址处存储的虚函数地址),然后跳转到该地址执行 ??_9Top@@$B3AE PROC ; Top::`vcall'{4}', COMDAT
mov eax, DWORD PTR [ecx];寄存器ecx保存有this指针,这里将this指针所指向的内存内容(即vftable首地址)
;给寄存器eax
jmp DWORD PTR [eax+];取偏移eax保存的内存地址4byte处的内存内容(即偏移虚表首地址4byte处内存存储的虚函数地址),然后跳转到该地址执行 ??_9Bottom@@$B7AE PROC ; Bottom::`vcall'{8}', COMDAT
mov eax, DWORD PTR [ecx];寄存器ecx保存有this指针,这里将this指针所指向的内存内容(即vftable首地址)
;给寄存器eax
jmp DWORD PTR [eax+];取偏移eax保存的内存地址8byte处的内存内容(即偏移虚表首地址8byte处内存存储的虚函数地址),然后跳转到该地址执行

下面是类Bottom的继承关系图(菱形继承)

下面是各个类的类存布局:

       

下面就来看看引入虚拟继承之后,成员函数指针存储的是什么值。

下面来看定义这些成员指针变量的汇编码:

   :     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+], ;将0给偏移临时对象ST4213首地址4byte处内存
mov DWORD PTR $T4213[ebp+], ;将4给偏移临时对象ST4213首地址8byte处内存
mov ecx, DWORD PTR $T4213[ebp];将临时对象ST4213首地址处内存内容给寄存器ecx
mov DWORD PTR _lgp1$[ebp], ecx;将寄存器ecx的值给lgp1首地址处内存
mov edx, DWORD PTR $T4213[ebp+];将偏移临时对象ST4213首地址4byte处内存内容给寄存器edx
mov DWORD PTR _lgp1$[ebp+], edx;将edx的值给偏移lgp1首地址4byte处内存
mov eax, DWORD PTR $T4213[ebp+];将偏移临时对象ST4213首地址8byte处内存内容给寄存器eax
mov DWORD PTR _lgp1$[ebp+], eax;将eax的值给偏移lgp1首地址8byte处内存 ; 63 : int(Left::*lgp2)() = &Left::get2;
;同定义lgp1
mov DWORD PTR $T4214[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'
mov DWORD PTR $T4214[ebp+],
mov DWORD PTR $T4214[ebp+],
mov ecx, DWORD PTR $T4214[ebp]
mov DWORD PTR _lgp2$[ebp], ecx
mov edx, DWORD PTR $T4214[ebp+]
mov DWORD PTR _lgp2$[ebp+], edx
mov eax, DWORD PTR $T4214[ebp+]
mov DWORD PTR _lgp2$[ebp+], eax ; 64 : int(Left::*lgp3)() = &Left::get3;
;同定义lgp1
mov DWORD PTR $T4215[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'
mov DWORD PTR $T4215[ebp+],
mov DWORD PTR $T4215[ebp+],
mov ecx, DWORD PTR $T4215[ebp]
mov DWORD PTR _lgp3$[ebp], ecx
mov edx, DWORD PTR $T4215[ebp+]
mov DWORD PTR _lgp3$[ebp+], edx
mov eax, DWORD PTR $T4215[ebp+]
mov DWORD PTR _lgp3$[ebp+], eax ; 65 : int(Left::*lgp4)() = &Left::get4;
;同定义lgp1 mov DWORD PTR $T4216[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'
mov DWORD PTR $T4216[ebp+],
mov DWORD PTR $T4216[ebp+],
mov ecx, DWORD PTR $T4216[ebp]
mov DWORD PTR _lgp4$[ebp], ecx
mov edx, DWORD PTR $T4216[ebp+]
mov DWORD PTR _lgp4$[ebp+], edx
mov eax, DWORD PTR $T4216[ebp+]
mov DWORD PTR _lgp4$[ebp+], eax ; 66 : int(Right::*rgp1)() = &Right::get1;
;同定义lgp1
mov DWORD PTR $T4217[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'
mov DWORD PTR $T4217[ebp+],
mov DWORD PTR $T4217[ebp+],
mov ecx, DWORD PTR $T4217[ebp]
mov DWORD PTR _rgp1$[ebp], ecx
mov edx, DWORD PTR $T4217[ebp+]
mov DWORD PTR _rgp1$[ebp+], edx
mov eax, DWORD PTR $T4217[ebp+]
mov DWORD PTR _rgp1$[ebp+], eax ; 67 : int(Right::*rgp2)() = &Right::get2;
;同定义lgp1
mov DWORD PTR $T4218[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'
mov DWORD PTR $T4218[ebp+],
mov DWORD PTR $T4218[ebp+],
mov ecx, DWORD PTR $T4218[ebp]
mov DWORD PTR _rgp2$[ebp], ecx
mov edx, DWORD PTR $T4218[ebp+]
mov DWORD PTR _rgp2$[ebp+], edx
mov eax, DWORD PTR $T4218[ebp+]
mov DWORD PTR _rgp2$[ebp+], eax ; 68 : int(Right::*rgp5)() = &Right::get5;
;同定义lgp1
mov DWORD PTR $T4219[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'
mov DWORD PTR $T4219[ebp+],
mov DWORD PTR $T4219[ebp+],
mov ecx, DWORD PTR $T4219[ebp]
mov DWORD PTR _rgp5$[ebp], ecx
mov edx, DWORD PTR $T4219[ebp+]
mov DWORD PTR _rgp5$[ebp+], edx
mov eax, DWORD PTR $T4219[ebp+]
mov DWORD PTR _rgp5$[ebp+], eax ; 69 : int(Right::*rgp6)() = &Right::get6;
;同定义lgp1
mov DWORD PTR $T4220[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'
mov DWORD PTR $T4220[ebp+],
mov DWORD PTR $T4220[ebp+],
mov ecx, DWORD PTR $T4220[ebp]
mov DWORD PTR _rgp6$[ebp], ecx
mov edx, DWORD PTR $T4220[ebp+]
mov DWORD PTR _rgp6$[ebp+], edx
mov eax, DWORD PTR $T4220[ebp+]
mov DWORD PTR _rgp6$[ebp+], eax ; 70 : int(Bottom::*bgp1)() = &Bottom::get1;
;同定义lgp1
mov DWORD PTR $T4221[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'
mov DWORD PTR $T4221[ebp+],
mov DWORD PTR $T4221[ebp+],
mov ecx, DWORD PTR $T4221[ebp]
mov DWORD PTR _bgp1$[ebp], ecx
mov edx, DWORD PTR $T4221[ebp+]
mov DWORD PTR _bgp1$[ebp+], edx
mov eax, DWORD PTR $T4221[ebp+]
mov DWORD PTR _bgp1$[ebp+], eax ; 71 : int(Bottom::*bgp2)() = &Bottom::get2;
;同定义lgp1
mov DWORD PTR $T4222[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'
mov DWORD PTR $T4222[ebp+],
mov DWORD PTR $T4222[ebp+],
mov ecx, DWORD PTR $T4222[ebp]
mov DWORD PTR _bgp2$[ebp], ecx
mov edx, DWORD PTR $T4222[ebp+]
mov DWORD PTR _bgp2$[ebp+], edx
mov eax, DWORD PTR $T4222[ebp+]
mov DWORD PTR _bgp2$[ebp+], eax ; 72 : int(Bottom::*bgp3)() = &Bottom::get3;
;同定义lg1
mov DWORD PTR $T4223[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'
mov DWORD PTR $T4223[ebp+],
mov DWORD PTR $T4223[ebp+],
mov ecx, DWORD PTR $T4223[ebp]
mov DWORD PTR _bgp3$[ebp], ecx
mov edx, DWORD PTR $T4223[ebp+]
mov DWORD PTR _bgp3$[ebp+], edx
mov eax, DWORD PTR $T4223[ebp+]
mov DWORD PTR _bgp3$[ebp+], eax ; 73 : int(Bottom::*bgp4)() = &Bottom::get4;
;同定义lgp1
mov DWORD PTR $T4224[ebp], OFFSET ??_9Top@@$B3AE ; Top::`vcall'{4}'
mov DWORD PTR $T4224[ebp+],
mov DWORD PTR $T4224[ebp+],
mov ecx, DWORD PTR $T4224[ebp]
mov DWORD PTR _bgp4$[ebp], ecx
mov edx, DWORD PTR $T4224[ebp+]
mov DWORD PTR _bgp4$[ebp+], edx
mov eax, DWORD PTR $T4224[ebp+]
mov DWORD PTR _bgp4$[ebp+], eax ; 74 : int(Bottom::*bgp5)() = &Bottom::get5;
;同定义lgp1
mov DWORD PTR $T4225[ebp], OFFSET ??_9Top@@$BA@AE ; Top::`vcall'{0}'
mov DWORD PTR $T4225[ebp+],
mov DWORD PTR $T4225[ebp+],
mov ecx, DWORD PTR $T4225[ebp]
mov DWORD PTR _bgp5$[ebp], ecx
mov edx, DWORD PTR $T4225[ebp+]
mov DWORD PTR _bgp5$[ebp+], edx
mov eax, DWORD PTR $T4225[ebp+]
mov DWORD PTR _bgp5$[ebp+], 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里面的值为什么,都为0
jne SHORT $LN7@main;如果上面异或结果不为0,就跳转到标号$LN7@main处执行,否则,顺序执行,这里显然顺序执行
mov DWORD PTR tv90[ebp], ;将8给临时变量tv90
jmp SHORT $LN8@main;跳转到标号$LN8@main处执行
$LN7@main:
mov DWORD PTR tv90[ebp],
$LN8@main:
mov edx, DWORD PTR tv90[ebp];将临时变量tv90的值给寄存器edx
mov DWORD PTR $T4226[ebp+], edx;i将edx的值给偏移临时对象ST4226首地址4byte处内存
mov DWORD PTR $T4226[ebp+], ;将0给偏移临时对象ST4226首地址8byte处内存
mov eax, DWORD PTR $T4226[ebp];将临时对象ST4226首地址处内存内容给寄存器eax
mov DWORD PTR _bgp6$[ebp], eax;将寄存器eax的内容给bgp6首地址处内存
mov ecx, DWORD PTR $T4226[ebp+];将偏移临时对象ST4226首地址4byte处内存内容给寄存器ecx
mov DWORD PTR _bgp6$[ebp+], ecx;将ecx的值给偏移bgp6首地址4byte处内存
mov edx, DWORD PTR $T4226[ebp+];将偏移临时对象ST4226首地址8byte处内存给寄存器edx
mov DWORD PTR _bgp6$[ebp+], edx;将edx的值给偏移bgp6首地址8byte处内存 ; 76 : int(Bottom::*bgp7)() = &Bottom::get7;
;同定义lgp1
mov DWORD PTR $T4229[ebp], OFFSET ??_9Bottom@@$B7AE ; Bottom::`vcall'{8}'
mov DWORD PTR $T4229[ebp+],
mov DWORD PTR $T4229[ebp+],
mov eax, DWORD PTR $T4229[ebp]
mov DWORD PTR _bgp7$[ebp], eax
mov ecx, DWORD PTR $T4229[ebp+]
mov DWORD PTR _bgp7$[ebp+], ecx
mov edx, DWORD PTR $T4229[ebp+]
mov DWORD PTR _bgp7$[ebp+], edx

下面给出给成员函数指针保存的值的图示

               

                             

           

成员函数指针保存的值清楚了,但是这些值又有什么意义呢?下面我们通过bgp1~bgp7成员函数指针来调用虚函数的汇编码来进行分析:

  :     (bp->*bgp1)();

    mov    edx, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器edx
mov eax, DWORD PTR [edx+];edx+4得到偏移对象b首地址4byte处内存地址,这里将该内存地址内容(vbtable首地址)给寄存器eax
mov ecx, DWORD PTR _bgp1$[ebp+];将偏移对象bgp1首地址8byte处内存内容给寄存器ecx
mov edx, DWORD PTR [eax+ecx];eax = vbtable首地址 ecx = 4 eax + ecx得到偏移vbtable首地址4byte处内存地址
;这里取偏移vbtable首地址4byte处内存内容(即vbtable指针偏移虚基类Top子对象首地址的偏移量)给寄存器edx
mov eax, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器eax
lea ecx, DWORD PTR [eax+edx+];eax = 对象b首地址 edx = 即vbtable指针偏移虚基类Top子对象首地址的偏移量 eax + edx + 4 = 对象b中虚基类Top子对象的首地址
add ecx, DWORD PTR _bgp1$[ebp+];将偏移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+]
mov eax, DWORD PTR _bgp2$[ebp+]
mov ecx, DWORD PTR [edx+eax]
mov edx, DWORD PTR _bp$[ebp]
lea ecx, DWORD PTR [edx+ecx+]
add ecx, DWORD PTR _bgp2$[ebp+]
call DWORD PTR _bgp2$[ebp] ; 123 : (bp->*bgp3)(); mov eax, DWORD PTR _bp$[ebp];将对象b的首地址给eax寄存器
mov ecx, DWORD PTR [eax+];eax = 对象b首地址 eax + 4 = 偏移对象b首地址4byte处内存地址
;这里将偏移对象b对象首地址4byte内存地址内容(即vbtable首地址)给寄存器ecx
mov edx, DWORD PTR _bgp3$[ebp+];将偏移对象bgp3首地址8byte处内存内容(为0)给寄存器edx
mov eax, DWORD PTR [ecx+edx];ecx = vbtable首地址 edx = 0 ecx + edx = vbtable首地址
;这里取vbtable首地址处的内存内容(即vbtable指针偏移对象b首地址的偏移量,为-4)给寄存器eax
mov ecx, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器ecx
lea ecx, DWORD PTR [ecx+eax+];ecx = 对象b首地址 eax = -4 ecx + eax + 4 = ecx(即对象b首地址)
;这里将对象b首地址给寄存器ecx
add ecx, DWORD PTR _bgp3$[ebp+];将偏移对象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+]
mov ecx, DWORD PTR _bgp4$[ebp+]
mov edx, DWORD PTR [eax+ecx]
mov eax, DWORD PTR _bp$[ebp]
lea ecx, DWORD PTR [eax+edx+]
add ecx, DWORD PTR _bgp4$[ebp+]
call DWORD PTR _bgp4$[ebp] ; 125 : (bp->*bgp5)();
;同bgp3调用
mov ecx, DWORD PTR _bp$[ebp]
mov edx, DWORD PTR [ecx+]
mov eax, DWORD PTR _bgp5$[ebp+]
mov ecx, DWORD PTR [edx+eax]
mov edx, DWORD PTR _bp$[ebp]
lea ecx, DWORD PTR [edx+ecx+]
add ecx, DWORD PTR _bgp5$[ebp+]
call DWORD PTR _bgp5$[ebp] ; 126 : (bp->*bgp6)();
;同bgp3调用
mov eax, DWORD PTR _bp$[ebp]
mov ecx, DWORD PTR [eax+]
mov edx, DWORD PTR _bgp6$[ebp+]
mov eax, DWORD PTR [ecx+edx]
mov ecx, DWORD PTR _bp$[ebp]
lea ecx, DWORD PTR [ecx+eax+]
add ecx, DWORD PTR _bgp6$[ebp+]
call DWORD PTR _bgp6$[ebp] ; 127 : (bp->*bgp7)();
;同bgp3调用
mov edx, DWORD PTR _bp$[ebp]
mov eax, DWORD PTR [edx+]
mov ecx, DWORD PTR _bgp7$[ebp+]
mov edx, DWORD PTR [eax+ecx]
mov eax, DWORD PTR _bp$[ebp]
lea ecx, DWORD PTR [eax+edx+]
add ecx, DWORD PTR _bgp7$[ebp+]
call DWORD PTR _bgp7$[ebp]

通过上面的汇编码可以发现,包含虚拟继承的成员函数指针第一项保存的仍然是相应vcall函数的地址,第二项保存的是成员函数所属类相对于对象b首地址的偏移量(但是指向虚基类成员函数的指针有所不同,如bgp1和bgp2,他们保存的是成员函数所属类相对于虚基类Top首地址的偏移量),第三项保存的是相对于vbtable首地址的偏移量。

可以看到,与普通的多重继承相比,包含虚拟继承的成员函数指针还保留了有关vbtable的信息,这是因为,由于虚基类的特殊性(即它的位置在每次子类派生之后,都不一样,而非虚基类在子类中都有固定的偏移量),想要定位虚基类的首地址,必须通过vbtable。

从汇编看c++中成员函数指针(一)从汇编看c++成员函数指针(二)讨论的一样,包含虚拟继承的时候,也可以将基类成员函数指针绑定到派生类对象或者派生类对象指针上,如可以(bp->*lgp1)(),编译器做内部转话(即将this指着从指向派生类对象首地址调整到指向派生类中基类子对象首地址处),但是不能将派生类成员函数指针绑定到基类对象或者基类对象指针上。

从汇编看c++中成员函数指针(一)从汇编看c++成员函数指针(二)讨论的一样,包含虚拟继承的时候,也可以将基类成员成员指针转换为派生类成员函数指针,如可以bgp1 = lgp1,但是不能讲派生类成员函数指针转换为基类成员函数指针。

从汇编看c++成员函数指针(三)的更多相关文章

  1. 从汇编看c++成员函数指针(二)

    下面先看一段c++源码: #include <cstdio> using namespace std; class X { public: virtual int get1() { ; } ...

  2. 函数指针和成员函数指针有什么不同,反汇编带看清成员函数指针的本尊(gcc@x64平台)

    函数指针是什么,可能会答指向函数的指针. 成员函数指针是什么,答指向成员函数的指针. 成员函数指针和函数指针有什么不同? 虚函数指针和非虚成员函数指针有什么不同? 你真正了解成员函数指针了吗? 本篇带 ...

  3. 成员函数指针与高性能C++委托

    1 引子 标准C++中没有真正的面向对象的函数指针.这一点对C++来说是不幸的,因为面向对象的指针(也叫做“闭包(closure)”或“委托(delegate)”)在一些语言中已经证明了它宝贵的价值. ...

  4. 自制反汇编工具使用实例 其二(使用xmm寄存器初始化对象,以及空的成员函数指针)

    在反汇编代码中,当看到xmm寄存器,第一反应是将要进行浮点操作或访问,但是更加多的情况是在使用xmm寄存器初始化局部对象. 下面是自制反汇编工具翻译出来的代码: // -[CALayer setAll ...

  5. 从汇编看c++中成员函数指针(一)

    下面先来看c++的源码: #include <cstdio> using namespace std; class X { public: int get1() { ; } virtual ...

  6. C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

    今天做一个成绩管理系统的并发引擎,用Qt做的,仿照QtConcurrent搞了个模板基类.这里为了隐藏细节,隔离变化,把并发的东西全部包含在模板基类中.子类只需注册需要并发执行的入口函数即可在单独线程 ...

  7. 成员函数指针与高效C++委托 (delegate)

    下载实例源代码 - 18.5 Kb 下载开发包库文件 - 18.6 Kb 概要 很遗憾, C++ 标准中没能提供面向对象的函数指针. 面向对象的函数指针也被称为闭包(closures) 或委托(del ...

  8. [转]成员函数指针与高性能的C++委托

    原文(作者:Don Clugston):Member Function Pointers and the Fastest Possible C++ Delegates 译文(作者:周翔): 成员函数指 ...

  9. 关于C++中的非静态类成员函数指针

    昨天发现了一个问题,就是使用对类中的非静态成员函数使用std::bind时,不能像普通函数一样直接传递函数名,而是必须显式地调用&(取地址),于是引申出我们今天的问题:非静态类成员函数指针和普 ...

随机推荐

  1. POJ 2112 Optimal Milking (二分 + 最大流)

    题目大意: 在一个农场里面,有k个挤奶机,编号分别是 1..k,有c头奶牛,编号分别是k+1 .. k+c,每个挤奶机一天最让可以挤m头奶牛的奶,奶牛和挤奶机之间用邻接矩阵给出距离.求让所有奶牛都挤到 ...

  2. css架构目标:预测,重用,扩展,维护

    请参看下面链接: CSS架构目标:预测.重用.扩展.维护

  3. oracle 时间比较查询

    select * from table  where add_time>=to_date('2015/01/03','yyyy/mm/dd')

  4. 自动运行native2ascii 命令的Bat文件的编写

        使用eclipse开发,对于.properties文件的国际化,如果不使用插件对文件进行转码,则需要使用native2ascii命令自行对文件进行转码.     为了更方面的执行此操作,我将该 ...

  5. 淘管 ERP项目与淘宝对接中产生的若干问题及处理办法

    现象:ERP绑定淘宝后,下载商品数据时如果成功,ajax不断尝试重发. 原因:  /app/taoapi/lib/top/TopClient.php 中的curl()方法成功后,返回空值,而前端收到空 ...

  6. XML的特殊字符

    XML中共有5个特殊的字符,分别是:&<>“’.如果配置文件中的注入值包括这些特殊字符,就需要进行特别处理.有两种解决方法: 其一,采用本例中的<![CDATA[ ]]> ...

  7. Dividing (hdu 1059 多重背包)

    Dividing Sample Input 1 0 1 2 0 0 价值为1,2,3,4,5,6的物品数目分别为 1 0 1 2 0 0,求能否将这些物品按价值分为两堆,转化为多重背包.1 0 0 0 ...

  8. 利用raspberry pi搭建typecho笔记(二) sqlite和typecho部署

    sqlite概述 typecho可以支持MYSQL和Sqlite两种数据库,因为Sqlite更为轻量,并且不需要额外的进程,而是直接对数据库文件进行读取,所以配置相对于MySQL也更为简单,仅需指定数 ...

  9. 把所有的QT的类都过一遍脑子

    http://doc.qt.io/qt-5/classes.html 这样就明白QT提供的全部功能了,避免重复造轮子,或者给自己开发带来麻烦-

  10. form-validation-engine中的正则表达式

    form-validation-engine是一个不错的表单验证,可以玩玩. (function($) { $.fn.validationEngineLanguage = function() {}; ...