`
lovnet
  • 浏览: 6704845 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

C++ 虚函数机制分析

 
阅读更多

原文: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

测试程序框架^

  1. 测试类声明

    程序中有 5 个类:Shape、Shape 的子类 RectCircleColor、Color 和 Circle 的子类ColorCircle,类声明如下:

    我将 VIRTUAL 定义为关键字 virtual 的宏,用于方便对比测试:使用和不使用虚函数时的调用方式

  2. 测试类成员定义

    在 Shape 类的继承格中,子类 Circle 和 Rect 只覆盖了 print_name()、draw() 两个父类成员函数,没有覆盖父类的 rotate() 函数,并且覆盖了父类 Shape 的类静态数据成员 s_DefaultName。在 ColorCircle 类的继承格中,ColorCircle 使用直接父类 Color 和 Circle 的成员,没有进行覆盖。这 5 个类的成员定义如下:

  3. 测试代码

    用于测试虚函数调用机制的基本代码如下:

普通非虚成员函数的调用^

在测试代码中,将下面的宏定义如下:

以激活对普通非虚成员函数调用的测试代码,普通成员函数使用 this call 调用约定的静态绑定(或早绑定)方式调用。所谓静态绑定就是在编译时已经确定了被调函数的地址,我在测试代码中找到这些成员函数地址,然后显式地写在程序中以模拟静态绑定行为,实验结果说明手工指定地址和编译产生的程序行为是一样的。下面是输出结果和相关的注释:

  1. 类静态数据成员

  2. 类对象的存储

    类对象中只存储类定义中非静态数据成员,以及虚表指针 vptr(如果该类是多态类)

    类定义中的静态数据成员、成员函数都是类自身的信息,和具体的类实例对象无关。对于成员函数:

    • 非静态的成员函数:使用 this call 调用约定,这意味着在调用时才会需要对象的信息,函数的实现代码是类自身的信息
    • 静态的成员函数:根据声明可以使用 cdec call、std call 等调用约定,而不使用 this call 调用约定。静态成员函数的调用、实现代码都和对象无关,所以它和全局函数的调用机制是相同的
    • 虚函数:使用 this call 调用约定,和动态绑定机制实现,虚表指针保存在对象本体 (this) 中。因此,一个函数不可能即是静态的,又是虚的,几乎所有的 C++ 编译器都能发现这种错误

    下面是测试中的对象存储:

  3. 静态绑定测试

  4. 模拟静态绑定

    将上面静态绑定测试中得到的函数地址,手工输入到源代码中,测试调用行为:

虚函数的调用^

测试代码中定义如下宏:

以激活对虚函数调用的测试代码。下面是输出结果和相关的注释:

  1. 多态对象的存储

  2. 虚函数表

  3. 动态绑定测试

  4. 模拟动态绑定

    模拟代码中,使用虚函数序号 1,即对应虚函数表偏移为 0 的函数地址,相关代码如下:

    运行结果如下:

    从上面结果可知,在多继承情况下,虽然虚函数序号都为 1,但在运行时,动态绑定机制会根据具体的 RTTI 信息(本例中成员函数指针的类型关联了 RTTI)选择合适的 vptr,即 vtbl 中合适的虚函数偏移起始地址,最后计算出类型相关的实际调用地址

动态绑定机制总结

虚函数的动态绑定机制,和 3 个东西相关:

  • 虚函数表 vtbl:它是多态类自身的信息,和具体的对象无关。在多继承情况下,虚表中罗列继承自每个父类的虚函数地址。如果子类直接使用父类中的虚函数定义,不进行覆盖,则子类、父类的虚函数地址就会相同;如果子类进行覆盖虚函数,vtbl 中对应的虚函数地址就会替换成子类自己的函数地址

  • 虚表指针 vptr:它保存 vtbl 的地址,每个多态类的对象存储中都会有 vptr。并且在多继承情况下,会存在多个 vptr。多继承子类对象,是按续构造父类对象后的拼接对象,再加上子类特有的存储

  • 虚函数序号:它是配合 vtbl 实现动态绑定的重要元素,它在编译时就确定了。在运行时,会根据 RTTI 来选择合适的 vptr,配合虚函数序号,计算 vtbl 中的存储虚函数地址的位置,最后找到虚函数的实际调用地址,进行 this call 调用

分享到:
评论

相关推荐

    C++虚函数的实现机制分析

    本文针对C++的虚函数的实现机制进行较为深入的分析,具体如下: 1、简单地说,虚函数是通过虚函数表实现的。那么,什么是虚函数表呢? 事实上,如果一个类中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚...

    C 实现计算机自动重启

    C++虚函数机制分析及实现 C++二进制文件读写详细演示 C++ 实现计算机自动重启 C++实现简单的密码输入程序 简单的C++程序注入实现 C++简单实现自删除演示及其代码 C++打造属于自己个性计算器 C++窗口的动画3D...

    深入C++虚表(虚函数 虚表 反汇编)

    许多C++语言的教材对于虚函数的使用以及调用机制有着详细的阐述,但是对于虚表的一些细节内容阐述却并不是很深,对于虚表我们可能会有很多疑问。本文就试图通过使用汇编语言对于虚表实现的细节进行分析,从而加深对...

    c++多态机制分析(虚函数的使用机制)

    对于C++中的多态机制进行了分析,并用例子进行了说明

    C++ 多态 虚表 分析 图解 .doc

    C++ 中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技 术可以让父类的指针有“多种形态”,这是一种泛型技术...

    C++面向对象特性实现机制的初步分析

    C++面向对象特性实现机制的初步分析 &lt;br/&gt;1准备知识 1.1 程序对内存的使用方法 1.2 C++ Class内存格局 1.3 编译期和执行期 &lt;br/&gt;2封装 2.1 封装的目的和意义 2.2 封装的实现机制 ...

    面向接口的C++编程技术研究

    分析了目前C++开发的2种通知机制的问题,包括使用虚函数继承实现接收通知导致的不符合常识的问题,以及使用组件类与客户类紧耦合导致的独立性差的问题。针对以上问题,结合目前主流的面向接口的编程技术,给出了在C++下...

    现代C++程序设计

    8.5.3 虚函数的作用 8.6 总结 8.7 练习 复习题 附录A 学习使用Visual C++2005Express Edition 附录B C++关键字表 附录C C++运算符 附录D ASCII码 附录E 位、字节、内存和十六进制表示 附录F 文件输入/输出 附录G ...

    多重继承机制.vsdx

    《C++对象模型探索 —— 多重继承虚函数表分析》博客的画图原文档。

    【全新正版】现代C++程序设计(原书第2版)

    8.5.3 虚函数的作用 8.6 总结 8.7 练习 复习题 附录A 学习使用Visual C++2005Express Edition 附录B C++关键字表 附录C C++运算符 附录D ASCII码 附录E 位、字节、内存和十六进制表示 附录F 文件输入/输出 附录G ...

    解析C++中的虚拟函数及其静态类型和动态类型

    虚拟函数是C++语言引入的一个很重要的特性,它提供了“动态绑定”机制,正是这一机制使得继承的语义变得相对明晰。 (1)基类抽象了通用的数据及操作,就数据而言,如果该数据成员在各派生类中都需要用到,那么就...

    新手学习C++入门资料

    C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。 二、关键字和变量 C++相对与C增加了一些关键字,如下: typename bool dynamic_cast mutable namespace static_cast using ...

    Visual C++ 2005入门经典.part08.rar (整理并添加所有书签)

    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 类和...

    Visual C++ 2005入门经典.part04.rar (整理并添加所有书签)

    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 类和...

    Visual C++ 2005入门经典.part05.rar (整理并添加所有书签)

    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 类和...

    C++ 多重继承和虚拟继承对象模型、效率分析

    通过带有虚函数的单一继承我们可以清楚的理解继承的概念、对象模型的分布机制以及动态绑定的发生,即可以完全彻底地理解多态的思想。为了支持多态,语言实现必须在时间和空间上付出额外的代价(毕竟没有免费的晚餐,...

    Visual C++实践与提高-COM和COM+篇『PDF』

    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库...

    Visual C++ 数据库系统开发完全手册.part2

    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应用...

    Visual C++ 数据库系统开发完全手册.part1

    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应用...

Global site tag (gtag.js) - Google Analytics