原文:C++ 虚函数机制分析
作者:Breaker <breaker.zy_AT_gmail>
C++ 中的虚函数调用机制通常是靠虚函数表 (vtbl) 和虚表指针 (vptr) 实现的,调用行为称为 晚绑定 (later binding)、动态绑定 (dynamic binding) 或 运行时绑定 (runtime binding),Bjarne Stroustrup 的《C++ 程序设计语言》章节 2.5.5 和 第 15 章(类层次结构)中提到虚函数表机制,但没有做详细的分析,他说虚函数的调用机制和 C++ 语言实现有关(指编译器),虚函数的实现方式也不一定是虚函数表
好书:"Inside the C++ Object Model" by Stanley Lippman,中文《深度探索 C++ 对象模型》,侯捷 译,在 C++ 程序员圈子中口碑很好,内容专注于 C++ 语言的底层机制,也包括虚函数、虚继承等 C++ 多态行为
这是自己用 g++ 编译、gdb 调试、分析的 C++ 虚函数工作机制,感觉有些收获,比单纯看书踏实。未涉及:包括多继承,但不包括虚继承(钻石继承)
关键字:调试分析, 字节存储, 继承, 多继承, 动态绑定, 虚函数, 虚函数表 vtbl, 虚表指针 vptr
DIY: Do It Yourself
目录
实验平台
系统:Windows XP Pro
编译工具:Cygwin g++ 4.3.4
调试工具:Cygwin gdb 6.8.0
测试程序框架
-
测试类声明
程序中有 5 个类:Shape、Shape 的子类 Rect 和 Circle,Color、Color 和 Circle 的子类ColorCircle,类声明如下:
我将 VIRTUAL 定义为关键字 virtual 的宏,用于方便对比测试:使用和不使用虚函数时的调用方式
-
测试类成员定义
在 Shape 类的继承格中,子类 Circle 和 Rect 只覆盖了 print_name()、draw() 两个父类成员函数,没有覆盖父类的 rotate() 函数,并且覆盖了父类 Shape 的类静态数据成员 s_DefaultName。在 ColorCircle 类的继承格中,ColorCircle 使用直接父类 Color 和 Circle 的成员,没有进行覆盖。这 5 个类的成员定义如下:
-
测试代码
用于测试虚函数调用机制的基本代码如下:
普通非虚成员函数的调用
在测试代码中,将下面的宏定义如下:
以激活对普通非虚成员函数调用的测试代码,普通成员函数使用 this call 调用约定的静态绑定(或早绑定)方式调用。所谓静态绑定就是在编译时已经确定了被调函数的地址,我在测试代码中找到这些成员函数地址,然后显式地写在程序中以模拟静态绑定行为,实验结果说明手工指定地址和编译产生的程序行为是一样的。下面是输出结果和相关的注释:
-
类静态数据成员
-
类对象的存储
类对象中只存储类定义中非静态数据成员,以及虚表指针 vptr(如果该类是多态类)
类定义中的静态数据成员、成员函数都是类自身的信息,和具体的类实例对象无关。对于成员函数:
-
非静态的成员函数:使用 this call 调用约定,这意味着在调用时才会需要对象的信息,函数的实现代码是类自身的信息
-
静态的成员函数:根据声明可以使用 cdec call、std call 等调用约定,而不使用 this call 调用约定。静态成员函数的调用、实现代码都和对象无关,所以它和全局函数的调用机制是相同的
-
虚函数:使用 this call 调用约定,和动态绑定机制实现,虚表指针保存在对象本体 (this) 中。因此,一个函数不可能即是静态的,又是虚的,几乎所有的 C++ 编译器都能发现这种错误
下面是测试中的对象存储:
-
静态绑定测试
-
模拟静态绑定
将上面静态绑定测试中得到的函数地址,手工输入到源代码中,测试调用行为:
虚函数的调用
测试代码中定义如下宏:
以激活对虚函数调用的测试代码。下面是输出结果和相关的注释:
-
多态对象的存储
-
虚函数表
-
动态绑定测试
-
模拟动态绑定
模拟代码中,使用虚函数序号 1,即对应虚函数表偏移为 0 的函数地址,相关代码如下:
运行结果如下:
从上面结果可知,在多继承情况下,虽然虚函数序号都为 1,但在运行时,动态绑定机制会根据具体的 RTTI 信息(本例中成员函数指针的类型关联了 RTTI)选择合适的 vptr,即 vtbl 中合适的虚函数偏移起始地址,最后计算出类型相关的实际调用地址
动态绑定机制总结
虚函数的动态绑定机制,和 3 个东西相关:
-
虚函数表 vtbl:它是多态类自身的信息,和具体的对象无关。在多继承情况下,虚表中罗列继承自每个父类的虚函数地址。如果子类直接使用父类中的虚函数定义,不进行覆盖,则子类、父类的虚函数地址就会相同;如果子类进行覆盖虚函数,vtbl 中对应的虚函数地址就会替换成子类自己的函数地址
-
虚表指针 vptr:它保存 vtbl 的地址,每个多态类的对象存储中都会有 vptr。并且在多继承情况下,会存在多个 vptr。多继承子类对象,是按续构造父类对象后的拼接对象,再加上子类特有的存储
-
虚函数序号:它是配合 vtbl 实现动态绑定的重要元素,它在编译时就确定了。在运行时,会根据 RTTI 来选择合适的 vptr,配合虚函数序号,计算 vtbl 中的存储虚函数地址的位置,最后找到虚函数的实际调用地址,进行 this call 调用
分享到:
相关推荐
本文针对C++的虚函数的实现机制进行较为深入的分析,具体如下: 1、简单地说,虚函数是通过虚函数表实现的。那么,什么是虚函数表呢? 事实上,如果一个类中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚...
C++虚函数机制分析及实现 C++二进制文件读写详细演示 C++ 实现计算机自动重启 C++实现简单的密码输入程序 简单的C++程序注入实现 C++简单实现自删除演示及其代码 C++打造属于自己个性计算器 C++窗口的动画3D...
许多C++语言的教材对于虚函数的使用以及调用机制有着详细的阐述,但是对于虚表的一些细节内容阐述却并不是很深,对于虚表我们可能会有很多疑问。本文就试图通过使用汇编语言对于虚表实现的细节进行分析,从而加深对...
对于C++中的多态机制进行了分析,并用例子进行了说明
C++ 中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技 术可以让父类的指针有“多种形态”,这是一种泛型技术...
C++面向对象特性实现机制的初步分析 <br/>1准备知识 1.1 程序对内存的使用方法 1.2 C++ Class内存格局 1.3 编译期和执行期 <br/>2封装 2.1 封装的目的和意义 2.2 封装的实现机制 ...
分析了目前C++开发的2种通知机制的问题,包括使用虚函数继承实现接收通知导致的不符合常识的问题,以及使用组件类与客户类紧耦合导致的独立性差的问题。针对以上问题,结合目前主流的面向接口的编程技术,给出了在C++下...
8.5.3 虚函数的作用 8.6 总结 8.7 练习 复习题 附录A 学习使用Visual C++2005Express Edition 附录B C++关键字表 附录C C++运算符 附录D ASCII码 附录E 位、字节、内存和十六进制表示 附录F 文件输入/输出 附录G ...
《C++对象模型探索 —— 多重继承虚函数表分析》博客的画图原文档。
8.5.3 虚函数的作用 8.6 总结 8.7 练习 复习题 附录A 学习使用Visual C++2005Express Edition 附录B C++关键字表 附录C C++运算符 附录D ASCII码 附录E 位、字节、内存和十六进制表示 附录F 文件输入/输出 附录G ...
虚拟函数是C++语言引入的一个很重要的特性,它提供了“动态绑定”机制,正是这一机制使得继承的语义变得相对明晰。 (1)基类抽象了通用的数据及操作,就数据而言,如果该数据成员在各派生类中都需要用到,那么就...
C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。 二、关键字和变量 C++相对与C增加了一些关键字,如下: typename bool dynamic_cast mutable namespace static_cast using ...
9.6.3 使用引用处理虚函数 9.6.4 纯虚函数 9.6.5 抽象类 9.6.6 间接基类 9.6.7 虚析构函数 9.7 类类型之间的强制转换 9.8 嵌套类 9.9 C++/CLI编程 9.9.1 C++/CLI类的继承 9.9.2 接口类 9.9.3 定义接口类 9.9.4 类和...
9.6.3 使用引用处理虚函数 9.6.4 纯虚函数 9.6.5 抽象类 9.6.6 间接基类 9.6.7 虚析构函数 9.7 类类型之间的强制转换 9.8 嵌套类 9.9 C++/CLI编程 9.9.1 C++/CLI类的继承 9.9.2 接口类 9.9.3 定义接口类 9.9.4 类和...
9.6.3 使用引用处理虚函数 9.6.4 纯虚函数 9.6.5 抽象类 9.6.6 间接基类 9.6.7 虚析构函数 9.7 类类型之间的强制转换 9.8 嵌套类 9.9 C++/CLI编程 9.9.1 C++/CLI类的继承 9.9.2 接口类 9.9.3 定义接口类 9.9.4 类和...
通过带有虚函数的单一继承我们可以清楚的理解继承的概念、对象模型的分布机制以及动态绑定的发生,即可以完全彻底地理解多态的思想。为了支持多态,语言实现必须在时间和空间上付出额外的代价(毕竟没有免费的晚餐,...
2.3.2.2 实现秘诀:虚函数(Virtual Functions) 2.3.3 使用抽象基类 2.3.4 例程实现 2.3.4.1 修改接口文件 2.3.4.2 修改对象程序 2.3.4.3 修改客户程序 2.4 改由COM库装载C++对象——例程dbalmostcom 2.4.1 COM库...
3.4 多态性与虚函数 3.5 抽象类与纯虚函数 3.6 C++模板 3.6.1 函数模板 3.6.2 类模板 第4章 创建应用程序 4.1 应用程序向导 4.1.1 Visual C++ 6.0中的向导类型 4.1.2 Visual C++ 6.0中的应用程序向导 4.1.3 MFC应用...
3.4 多态性与虚函数 3.5 抽象类与纯虚函数 3.6 C++模板 3.6.1 函数模板 3.6.2 类模板 第4章 创建应用程序 4.1 应用程序向导 4.1.1 Visual C++ 6.0中的向导类型 4.1.2 Visual C++ 6.0中的应用程序向导 4.1.3 MFC应用...