类的虚函数和非虚函数调用自己分析.doc_第1页
类的虚函数和非虚函数调用自己分析.doc_第2页
类的虚函数和非虚函数调用自己分析.doc_第3页
类的虚函数和非虚函数调用自己分析.doc_第4页
类的虚函数和非虚函数调用自己分析.doc_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

一、多重继承,C继承自A和B其类结构如下图(图2):虚表指针_vfptr(0x00AC780C)A数据成员区:A:m_i(0x11223344)B数据成员区:B:m_i(0x55667788)C数据成员区:C:m_i(0x99aabbcc)第一个虚函数的地址:A:VF() 0x00AC1122A:VF() 函数入口,实际为VF(A* const this)coutA:VF() endl;0x00AC780C0x00AC11220x0046B3F0下一个虚函数的地址:.函数入口:*0x*内存值内存地址内存值内存地址内存值内存地址A部分B部分类C上面代码中主函数如为:int _tmain(int argc, _TCHAR* argv)C* pc= new C;/pc=0x0046B3F0A* pa= pc;/pa=0x0046B3F0B* pb= pc;/pb=0x0046B3F0+8=0x0046B3F8system(pause);return 0; 对下面代码:int _tmain(int argc, _TCHAR* argv)B b;/设B的地址为XXXC* pc= ( C*)( &b);/相当于C* pc= static_cast( &b)。根据图2的类结构中C和B的位置关系/(编译器可能认为B的地址比C的地址大8,所以C的地址为B的地址减去8),可知pc=XXX-8。此时可以想像出右边的类Cpc-VF();/错误。如正确,在(XXX-8)处获取虚表指针,但pc存放的是其他数据,故无法调用虚函数,产生错误。system(pause);return 0;其他数据其他数据B:m_i(0x55667788)XXX内存值内存地址B类CXXX-8而下面代码却能正确执行:int _tmain(int argc, _TCHAR* argv)A a;/设A的地址为XXXC* pc= ( C*)( &a);/相当于C* pc= static_cast( &a)。根据图2的类结构中C和A的位置关系/(相等),可知pc=XXXpc-VF();/正确,有多态,调用的VF()为实际内存对象的函数,即调用A:VF()。在XXX处获取虚表指针,能正确调用虚函数VF()。coutm_im_i的值随机,/因为此处内存并未被C对象使用或赋值,而可能是其他对象的内存空间。system(pause);return 0;虚表指针_vfptrA数据成员区:A:m_i(0x11223344)C:m_i(未赋值)内存值内存地址A类CXXXA:VF() 函数入口,实际为VF(A* const this)。.而如果B类中也有虚函数,如:class Apublic:int m_i;A(): m_i( 0x11223344)virtual void VF()cout A:VF() endl;class Bpublic:int m_i;B(): m_i( 0x55667788)virtual void VF()cout B:VF() endl;virtual void VG()cout B:VG() endl;class C: public A, public Bpublic:int m_i;C(): m_i( 0x99aabbcc)virtual void VF()cout C:VF()VF();/直接在XXX处的A虚表指针所指的虚表中找到C:VG()并调用C:VG()pc-VG(); /因为VG()是B类中的,C类并未重写,所以先找到C类中B类起始地址(XXX+8),/再在(XXX+8)处的B虚表指针所指的虚表中找到B:VG()并调用B:VG()。可以参照下面的反汇编代码。B* pb= ( B*)( pc);/由下图3类结构知,pb=XXX+8,pb能访问内存大小为下图3中B部分的8字节(B虚表指针_vfptr和B数据成员B:m_i)pb-VG();/直接在(XXX+8)处的B虚表指针所指的虚表中找到B:VG()并调用B:VG()A* pa= ( A*)( pc);/由下图3类结构知,pa=XXX,pa能访问内存大小为下图3中A部分的8字节(A虚表指针_vfptr和A数据成员A:m_i)pa-VF();/直接在XXX处的A虚表指针所指的虚表中找到C:VF()并调用C:VF()system(pause);return 0;则类结构如图3:A虚表指针_vfptrA数据成员区:A:m_i(0x11223344)B虚表指针_vfptrXXX内存值内存地址A部分B部分类CB数据成员区:B:m_i(0x55667788)C数据成员区:C:m_i(0x99aabbcc)。.C:VF() 函数入口,实际为VF(C* const this)。.thunk A虚表中C:VF() 的引用XXX+8B:VG() 函数入口,实际为VG(B* const this)反汇编代码如下:pc-VF();00D219E4 mov eax,dword ptr pc 00D219E7 mov edx,dword ptr eax 00D219E9 mov esi,esp 00D219EB mov ecx,dword ptr pc 00D219EE mov eax,dword ptr edx 00D219F0 call eax 00D219F2 cmp esi,esp 00D219F4 call ILT+465(_RTC_CheckEsp) (0D211D6h) pc-VG();00D219F9 mov ecx,dword ptr pc 00D219FC add ecx,8 00D219FF mov eax,dword ptr pc 00D21A02 mov edx,dword ptr eax+8 00D21A05 mov esi,esp 00D21A07 mov eax,dword ptr edx+4 00D21A0A call eax 00D21A0C cmp esi,esp 00D21A0E call ILT+465(_RTC_CheckEsp) (0D211D6h)注意,图3中类C的A部分的A虚表指针_vfptr指向的虚函数表中只有一个C:VF(),而没有B:VG(),而深度探索C+对象模型P165图中Base1 subobject的虚表中却有Base2:mumble(),可能书中有错误。如下图,A虚表指针_vfptr为红框的0x00D27818,但0x00D27818处只有一个0x00D212B7(为C:VF()),右边就是00000000了,所以并无B:VG()。而图3中B虚表指针_vfptr所指的值(图3中蓝色字体及箭头)可能有几步组成,其反汇编为:011B12BC jmp thunk:C:VFadjustor8 (11B1630h)thunk:C:VFadjustor8:011B1630 sub ecx,8 011B1633 jmp C:VF (11B12B7h)011B12B7 jmp C:VF (11B4A10h) virtual void VF()/此处是C:VF()011B4A10 push ebp 实际上是先减去8(为B虚表地址与A虚表地址之差),到A虚表地址,再查找A虚表中的C:VF()。经实验,C的虚函数FF()重写后,并列继承(A、B)中B的FF()要引用A中的FF(),要用到thunk(),如B部分中B虚表指针_vfptr指向的C:VF()其实是A部分中A虚表指针_vfptr指向的C:VF()的引用。如果C未重写VF(),如下:class Apublic:int m_i;A(): m_i( 0x11223344)virtual void VF()cout A:VF() endl;class Bpublic:int m_i;B(): m_i( 0x55667788)virtual void VF()cout B:VF()VF();/参照下图4,在XXX处的A虚表指针所指的虚表中找到A:VG()并调用A:VF()pb-VF();/在(XXX+8)处的B虚表指针所指的虚表中找到B:VF()并调用B:VF()/pc-VF();/或C c; c.VF()均错误,不知道是调用A:VF()不是B:VF(),到底是VF( A* const p),还是VF( B* const p)。/如果C类重写了VF(),则无错误,像上一个代码例子一样。system(pause);return 0;其类结构图如图4:A虚表指针_vfptrA数据成员区:A:m_i(0x11223344)B虚表指针_vfptrXXX内存值内存地址A部分B部分类CB数据成员区:B:m_i(0x55667788)C数据成员区:C:m_i(0x99aabbcc)。.A:VF() 函数入口,实际为VF(A* const this)。.B:VF() 函数入口,实际为VF(B* const this)XXX+8B:VG() 函数入口,实际为VG(B* const this)对下面代码:class Apublic:int m_i;A(): m_i( 0x11223344)virtual void VF()cout A:VF() endl;class Bpublic:int m_i;B(): m_i( 0x55667788)virtual void VF()cout B:VF() endl;virtual void VG()cout B:VG() endl;class C: public A, public Bpublic:int m_i;C(): m_i( 0x99aabbcc)virtual void VF()cout C:VF() endl;virtual void VG()cout C:VG()VF();pb-VG();pc-VG();system(pause);return 0;部分反汇编代码如下:pb-VF();011A48AD mov eax,dword ptr ebp-20h 011A48B0 mov edx,dword ptr eax 011A48B2 mov esi,esp 011A48B4 mov ecx,dword ptr ebp-20h 011A48B7 mov eax,dword ptr edx 011A48B9 call eax 011A48BB cmp esi,esp 011A48BD call ILT+485(_RTC_CheckEsp) (11A11EAh) pb-VG();011A48C2 mov eax,dword ptr ebp-20h 011A48C5 mov edx,dword ptr eax 011A48C7 mov esi,esp 011A48C9 mov ecx,dword ptr ebp-20h 011A48CC mov eax,dword ptr edx+4 011A48CF call eax 011A48D1 cmp esi,esp 011A48D3 call ILT+485(_RTC_CheckEsp) (11A11EAh) pc-VG();011A48D8 mov ecx,dword ptr ebp-14h 011A48DB add ecx,8 011A48DE mov eax,dword ptr ebp-14h 011A48E1 mov edx,dword ptr eax+8 011A48E4 mov esi,esp 011A48E6 mov eax,dword ptr edx+4 011A48E9 call eax 011A48EB cmp esi,esp 011A48ED call ILT+485(_RTC_CheckEsp) (11A11EAh)类C结构图如图4:A虚表指针_vfptrA数据成员区:A:m_i(0x11223344)B虚表指针_vfptrXXX内存值内存地址A部分B部分类CB数据成员区:B:m_i(0x55667788)C数据成员区:C:m_i(0x99aabbcc)。.C:VF() 函数入口,实际为VF(C* const this)。.thunk A虚表中C:VF() 的引用XXX+8C:VG() 函数入口,实际为VG(C* const this)可见,pb-VG();是直接在B部分的起始处的虚表指针(eax)指向的虚表中查找第2个虚函数(edx+4)。而对pc-VG();因为VG()是B中最先声明的虚函数,所以先在C类中找到B部分(eax+8),再调用B的虚表中的第2个虚函数(eax+4)。可知B类中的虚函数是不会被加入到A虚表指针中的,因为本来A和B是不相关的。如VG()不是A的函数,是B的虚函数,且C重写了,但仍不会加到类C中A部分的虚表中。pc-VG()是先定位pc地址XXX,再移到B的虚表指针处(XXX+8),再调用B虚表中的VG()函数,由于B虚表中的VG()已经被重写为C:VG(),故最终调用C:VG()。如果C中定义了新的既不属于A,也不属于B的虚函数,该虚函数会被加在A虚表的后边。代码如下:class Apublic:int m_i;A(): m_i( 0x11223344)virtual void VF()cout A:VF() endl;class Bpublic:int m_i;B(): m_i( 0x55667788)virtual void VF()cout B:VF() endl;virtual void VG()cout B:VG() endl;class C: public A, public Bpublic:int m_i;C(): m_i( 0x99aabbcc)virtual void VF()cout C:VF() endl;virtual void VG()cout C:VG() endl;virtual void VH()cout C:VH() endl;其C类结构如下:A虚表指针_vfptrA数据成员区:A:m_i(0x11223344)B虚表指针_vfptrXXX内存值内存地址A部分B部分类CB数据成员区:B:m_i(0x55667788)C数据成员区:C:m_i(0x99aabbcc)。.C:VF() 函数入口,实际为VF(C* const this)。.thunk A虚表中C:VF() 的引用XXX+8C:VG() 函数入口,实际为VG(C* const this)C:VH() 函数入口,实际为VH(C* const this)二、而对下面的继承:class Apublic:int m_i;A(): m_i( 0x11223344)virtual void VF()cout A:VF()m_i= 0xdddddddd;A* pa= &c;/pa=0x002DF814pa-m_i= 0xeeeeeeee;执行后如图:可知,pa、pb都指向c的内存地址,pb-m_i= 0xdddddddd;改变的是B:m_i的值,因为pb是B类型指针,它访问的是上图中B数据成员区数据(地址为0x0046B3F8),如pb要访问A:m_i,则可pb-A:m_i= 0xdddddddd;。同理,pa-m_i= 0xeeeeeeee;访问的是A:m_i(地址为0x0046B3F4)。B* pb= &c;中pb是B类型指针,只能访问pb开始处,大小为sizeof(B)的数据,即上图中B部分的12个字节(地址从0x0046B3F00x0046B3FB),而pb是不能直接访问到C:m_i的(除非将pb转换为C*类型)。同理pa只能访问上图中A部分的8个字节(地址从0x0046B3F00x0046B3F7)。数据成员则没有多态从上可看出,数据成员是没有多态的,即虽然pb、pa实际指向的是c对象,但访问其数据成员时,pb-m_i和pa-m_i仍是根据pb、pa的指针声明类型(pb为B类型,pa为A类型)来访问B:m_i和A:m_i,而不是C:m_i。但虚函数多态不一样,pb-VirtualFunc()和pa-VirtualFunc()可能都调用C:VirtualFunc()(当然,如C类未覆盖(即重写)VirtualFunc(),则查找父类、祖父类的虚函数B:VirtualFu

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论