C++多态:深入虚函数,理解晚绑定
来源:互联网 发布:淘宝运营讲师 编辑:程序博客网 时间:2024/05/22 14:46
C++的多态特性是通过晚绑定实现的。晚绑定(late binding),指的是编译器或解释器程序在运行前,不知道对象的类型。使用晚绑定,无需检查对象的类型,只需要检查对象是否支持特性和方法即可。
在C++中,晚绑定通常发生在使用virtual
声明成员函数时。此时,C++创建一个虚函数表,当某个函数被调用时需要从这个表中查找该函数的实际位置。通常,晚绑定也叫做动态函数分派(dynamic dispatch)。
考虑如下的代码:
#include<iostream>using namespace std;class D {public: int num; D(int i = 0) { num = i; } virtual void print() { cout << "I'm a D. my num=" << num << endl; };};class E :public D {public: E(int i = 0) { num = i; } void print() { cout << "I'm a E. my num=" << num << endl; } void ppp() { int ttt = 1; }};int main(){ void (D::*i)() = &D::print; E* e = new E(1); e->print(); (((D*)e)->*i)(); delete e; return 0;}
输出结果为:
I'm a E. my num=1I'm a E. my num=1
使用VS命令/d1 reportSingleClassLayoutD和/d1 reportSingleClassLayoutE,可以得到类D和类E的内存布局。可以看到,D的大小是8个字节,头四个字节存储指向虚函数表的指针vfptr,后四个字节存储成员变量num。E的大小也是8个字节,头四个字节存储指向虚函数表的指针,后四个字节存储从基类继承的成员变量num。
1> class D size(8):1> +---1> 0 | {vfptr}1> 4 | num1> +---1>1> D::$vftable@:1> | &D_meta1> | 01> 0 | &D::print1> class E size(8):1> +---1> 0 | +--- (base class D)1> 0 | | {vfptr}1> 4 | | num1> | +---1> +---1>1> E::$vftable@:1> | &E_meta1> | 01> 0 | &E::print
内存布局图:
接下来从汇编角度解释一下晚绑定是怎么发生的。
int main(){000C27B0 push ebp 000C27B1 mov ebp,esp 000C27B3 push 0FFFFFFFFh 000C27B5 push 0C7242h 000C27BA mov eax,dword ptr fs:[00000000h] 000C27C0 push eax 000C27C1 sub esp,100h 000C27C7 push ebx 000C27C8 push esi 000C27C9 push edi 000C27CA lea edi,[ebp-10Ch] 000C27D0 mov ecx,40h 000C27D5 mov eax,0CCCCCCCCh 000C27DA rep stos dword ptr es:[edi] 000C27DC mov eax,dword ptr [__security_cookie (0CC004h)] 000C27E1 xor eax,ebp 000C27E3 push eax 000C27E4 lea eax,[ebp-0Ch] 000C27E7 mov dword ptr fs:[00000000h],eax void (D::*i)() = &D::print;//vcall是虚函数表,vcall{0}就是虚函数D::print(),这里把D::print()偏移地址赋给ptr[i]000C27ED mov dword ptr [i],offset D::`vcall'{0}' (0C146Fh) E* e = new E(1);000C27F4 push 8 000C27F6 call operator new (0C1311h) 000C27FB add esp,4 000C27FE mov dword ptr [ebp-0F8h],eax 000C2804 mov dword ptr [ebp-4],0 000C280B cmp dword ptr [ebp-0F8h],0 000C2812 je main+79h (0C2829h) 000C2814 push 1 000C2816 mov ecx,dword ptr [ebp-0F8h] 000C281C call E::E (0C137Fh) 000C2821 mov dword ptr [ebp-10Ch],eax 000C2827 jmp main+83h (0C2833h) 000C2829 mov dword ptr [ebp-10Ch],0 000C2833 mov eax,dword ptr [ebp-10Ch] 000C2839 mov dword ptr [ebp-0ECh],eax 000C283F mov dword ptr [ebp-4],0FFFFFFFFh 000C2846 mov ecx,dword ptr [ebp-0ECh] 000C284C mov dword ptr [e],ecx e->print();000C284F mov eax,dword ptr [e]//e的指针赋给eax 000C2852 mov edx,dword ptr [eax]//打开e的指针,e中vfptr存在头四个字节,所以edx获取vfptr000C2854 mov esi,esp 000C2856 mov ecx,dword ptr [e]//成员函数调用是this->func(),这里this指针(也就是e)存入ecx 000C2859 mov eax,dword ptr [edx]//因为vcall{0}就是函数print(),所以这里直接把edx存储的指针,也就是vfptr,解引用之后赋值给eax调用就可以了 e->print();000C285B call eax//调用eax指向的函数。由于这个过程是运行时确定的而不是编译时确定的,所以也叫动态函数分派,即晚绑定。(((D*)e)->*i)()更能体现动态性。000C285D cmp esi,esp 000C285F call __RTC_CheckEsp (0C1195h) (((D*)e)->*i)();000C2864 mov esi,esp 000C2866 mov ecx,dword ptr [e]//成员函数调用是this->func(),这里this指针(也就是e)存入ecx 000C2869 call dword ptr [i]//打开指针i,获取偏移地址。此时基址变成了e所在的内存段,所以配合ecx中的指针e获取的是E::print(),而不是D::print()。因为E重写了D的print()。也可以不重写,那样的话调用的就是D::print(),读者可以自己验证。000C286C cmp esi,esp 000C286E call __RTC_CheckEsp (0C1195h) delete e;000C2873 mov eax,dword ptr [e] 000C2876 mov dword ptr [ebp-104h],eax 000C287C push 8 000C287E mov ecx,dword ptr [ebp-104h] 000C2884 push ecx 000C2885 call operator delete (0C105Ah) 000C288A add esp,8 000C288D cmp dword ptr [ebp-104h],0 000C2894 jne main+0F2h (0C28A2h) 000C2896 mov dword ptr [ebp-10Ch],0 000C28A0 jmp main+102h (0C28B2h) 000C28A2 mov dword ptr [e],8123h 000C28A9 mov edx,dword ptr [e] 000C28AC mov dword ptr [ebp-10Ch],edx return 0;000C28B2 xor eax,eax }
总结
运行时多态通过多次对地址指针解引用,获得虚函数实体的地址,进而执行对应的虚函数。
多态配合泛型算法简化编程,见我的另一篇博文:http://blog.csdn.net/popvip44/article/details/72674326
阅读全文
0 0
- C++多态:深入虚函数,理解晚绑定
- 深入理解C函数声明
- C语言函数深入理解
- 深入理解虚函数
- 深入理解C++重载、多态、虚函数
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- 深入理解C/C++函数指针
- IFrame的使用
- 信号量
- 在win7中chm打不开的多种解决方法
- 管理用户配置文件++查询用户配置文件+修改用户配置文件+删除用户配置文件
- LINUX发展趣文
- C++多态:深入虚函数,理解晚绑定
- select2插件 空格 分词 模糊匹配 模糊搜索
- Java的引用数据类型(重要,面试)
- lintcode(186)最多有多少个点在一条直线上
- RichEdit的各个版本简介
- 资源列表
- IDEA破解 2017 IDEA license server 激活(可用)
- dp——洛谷P1133 教主的花园
- JVM进阶(一)——初识JAVA栈