


C/C++培训
达内IT学院
400-996-5531

虚函数是C++实现多态的机制,那么它是如何做到的呢?
以下通过反汇编探索虚函数内存模型,查看虚函数实现多态的过程。
工具
Visual studio 2017:以下程序仅做VC++编译器下的32位程序探讨,其他编译器与64位程序所产生的差异不作讨论。
反汇编过程
首先声明一个不包含虚函数的简单C++类,如下:
在构造函数中加入断点,使得反汇编构造函数代码,如图:
当运行到断点时,在Visual Studio中使用快捷键Ctrl+F11打开反汇编,得出如下指令:
黄色框中的指令可以看出,构造函数中,将4这个值放到了this指针所指向区域的前4个字节,而4这个值刚好就是变量a的值,也就是说,在不包含虚函数时,4个字节长度的int型变量a就存放在对象数据空间的起始4个字节。
随后再创建包含虚函数的类c2,如图:
用相同方法让程序暂停在c2类的构造函数的断点,再反汇编,同时对比c1反汇编指令:
左边为包含虚函数的类c2,右边为不包含虚函数的类c1
可以看出,包含虚函数的类c2的构造函数,相比不包含虚函数的类c1,仅仅多出了两条指令:
这两条指令表示,this指针的前4个字节,不再是存放变量a,而是一个vftable的地址(即虚函数表),而变量数据则是通过往后偏移4个字节存放,如对比图左图所示,所有地址都以加4来偏移:
由此可以总结出包含虚函数的类对象在内存中的模型:
如图,包含虚函数的类对象,前4个字节存放的是虚函数表,后面才开始存放变量。
那么虚函数表中存放的是什么内容?
从上面图片中显示,虚函数表指向的内存地址为01 3B 8B 34h,通过Visual Studio的调试-窗口-内存,打开内存查看工具,定位到这个地址,可以看到里面的内容:
很明显可以看出,选中的8个字节分别代表两段内存地址01 3b 10 0a和01 3b 13 ac,而不是程序指令集,因为程序数据所在内存地址都在01 3b ** **内,可以看到所有反汇编指令前的内存地址皆是如此。
为了弄明白这两个内存地址的内容,在反汇编窗口输入内存地址查看01 3b 10 0a:
可以看到,这个地址指向第一个虚函数c2::test(),再看第二个地址01 3b 13 ac:
可以看到,第二个地址指向第二个虚函数c2::test2()。
这说明,虚函数表实际上是一个存放虚函数指针的数组。
那么多态是怎么实现的呢?
再创建一个继承c2的子类cc2,同样反汇编查看构造函数:
可以看出,cc2构造函数中赋予的虚函数表地址与基类地址不同,并且所指向的虚函数也是cc2自己的虚函数,在cc2的构造函数中,先调用了基类c2的构造函数,如红色框所示,在基类c2的构造函数中,会将基类的虚函数表放在类对象前4个字节,随后从c2构造函数中出来,运行到黄色框部分,子类会再次将子类自己的虚函数表放在类对象前4个字节,从而将基类的虚函数表覆盖,实现多态。
总结
不包含虚函数的类,是没有虚函数表的,变量等数据是从类对象数据块的第一个字节开始存放。
包含虚函数的类,会多出一个虚函数表,而类对象数据块的前4个字节存放的是虚函数表地址,从第5个字节开始存放变量等数据。
虚函数表实质是一个函数指针的数组,存放着本类的各个虚函数的指针。
由于基类的构造函数先于子类执行,这会导致子类的虚函数表巧妙地覆盖掉基类的虚函数表,从而实现多态。
本文内容转载自网络,本着分享与传播的原则,版权归原作者所有,如有侵权请联系我们进行删除!
填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!
Copyright © 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有