C++动态联编解析_第1页
C++动态联编解析_第2页
C++动态联编解析_第3页
C++动态联编解析_第4页
C++动态联编解析_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

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

文档简介

1、多态性是C+最主要的特征,多态性的实现得益于C+中的动态联编技术。文章通过对动态联编的关键技术虚拟函数表进行深入的剖析,解析的动态联编的过程极其技术要领。 关键字 多态性 动态联编 VTABLE 虚函数 文章正文 一 从多态性谈动态联编的必要性在进入主题之前先介绍一下联编的概念。联编就是将模块或者函数合并在一起生成可 执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。在编译阶段就将函数实现和函数调用关联起来称之为静态联编,静态联编在编译阶段就必须了解所有的函数或模块执行所需要检

2、测的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型。反之在程序执行的时候才进行这种关联称之为动态联编,动态联编对成员函数的选择不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。C语言中,所有的联编都是静态联编。C+中一般情况下联编也是静态联编,但是一旦涉及到多态性和虚函数就必须使用动态联编。 多态性是面向对象的核心,它的最主要的思想就是可以采用多种形式的能力,通过一个用户名字或者用户接口完成不同的实现。通常多态性被简单的描述为"一个接口,多个实现。在C+里面具体的表现为通过基类指针访问派生类的函数和方法。 下面我们看一个静态联编的例子,这种静态联

3、编导致了我们不希望的结果。 /1.cpp1. #include <iostream.h>2. class shape3.   public:4.     void draw()cout<<"I am shape"<<endl;5.     void fun()draw();6. ;7. class circle:public shape8.   public:9.     void

4、 draw()cout<<"I am circle"<<endl;10. ;11. main()12.     class circle   oneshape;13.     oneshape.fun();14. 程序的输出结果我们希望是"I am circle",但事实上却输出了"I am shape"的结果,造成这个结果的原因是静态联编。静态联编需要在编译时候就确定函数的实现,但事实上编译器在仅仅知道shap

5、e的地址时候无法获取正确的调用函数,它所知道的仅是shape:draw(),最终结果只能是draw操作束缚到shape类上。产生"I am shape"的结果就不足为奇了。为了能够引起动态联编,我们只需要将需要动态联编的函数声明为虚函数即可。动态联编只对虚函数起作用。我们在通过基类而且只有通过基类访问派生类的时候,只要这个基类中直接的或者间接(从上上层继承)的包含虚函数,动态联编将自动唤醒。下面我们将上面的程序稍微改一下。 /2.cpp1. #include <iostream.h>2. class shape3.   public:4.&#

6、160;   virtual void draw()cout<<"I am shape"<<endl;5.     void fun()draw();6. ;7. class circle:public shape8.   public:9.     void draw()cout<<"I am circle"<<endl;10. ;11. main()12. 

7、0;   class circle   oneshape;13.     fun(&oneshape);14. 程序执行得到了正确的结果"I am circle"。代码在VC6.0中执行。 到目前为止我们不清楚动态联编的执行机制,但我们可以做个猜测。正如上面所说,对于函数的实际的对象类型不同,联编结果也应该不同。在静态联编中,执行的困难在于无法通过基类知道需要联编的子对象的确切类型。在1.cpp中shape的派生类既可能是circle,也可能是其余的rectangle或者square等等

8、,到底应该静态联编哪一个呢。迷惑正在于此。动态联编在编译的时候应该也是不知道联编的确切对象类型的,(如果知道的话就成了静态联编了),因此它只能通过一定的机制,使得在执行时候能够找到和调用正确的函数体。可以想象,为了达到这个目的,一些相关信息应该封装在对象自身中。这些信息有点象身份证明,标识自己,这样在动态联编的时候,编译器可以根据这些标记找到相应的函数体,"不要跑,就是你了"。 实际上的动态联编过程是什么样的呢。二 对象类型信息为了证明我们的猜想,我们用下面的一个程序进行测试,下面的程序将获取普通的类和包含虚函数的类的字节大小。程序代码如下。 /3.cpp1. #inclu

9、de <iostream.h>2. class shape_novirtual3.       int    a;4.    public:5.       void   draw()cout<<"shape_novirtual:draw()"<<endl;6. ;7. class shape_virtual18.  &#

10、160;    int    a;9.    public:10.       virtual void draw()cout<<"shape_virtual:draw()"<<endl;11. ;12. class shape_virtual213.       int   a;14.   

11、public:15.       virtual void draw()cout<<"shape_virtual2:draw()"<<endl;16.       virtual void draw1()cout<<"shape_virtual2:draw1()"<<endl;17. ;18. main()19.    cout<<"si

12、zeof(int)"<<sizeof(int)<<endl;20.    cout<<"sizeof(class shape_novirtual):"<<sizeof(shape_novirtual)<<endl;21.    cout<<"sizeof(void*):"<<sizeof(void*)<<endl;22.    cout<<"

13、;sizeof(class shape_virtual):"<<sizeof(shape_virtual)<<endl;23.    cout<<"sizeof(class shape_virtual2):"<<sizeof(shape_virtual2)<<endl;24. VC6.0中运行结果如下: sizeof(int)4sizeof(class shape_novirtual):4sizeof(void*):4sizeof(class shape_virtual1):

14、8sizeof(class shape_virtual2):8Press any key to continue从上面可以看出,没有虚函数的类shape_novirtual的大小为4,正好为int a的大小。而带有虚函数的类shape_virtual1和shape_virtual2的大小除了int a的大小还多出了4格个字节的大小,这个大小正好是void*指针的大小。到现在为止我们基本上可以说带有虚函数的对象自身确实插入了一些指针信息,而且这个指针信息并不随着虚函数的增加而增大。如果我们将每个类的成员变量int a去掉,VC6.0运行结果就会变成下面的情况。 sizeof(int)4sizeo

15、f(class shape_novirtual):1sizeof(void*):4sizeof(class shape_virtual1):4sizeof(class shape_virtual2):4Press any key to continue上面的运行结果应该让人感到例外。既然size(int)为4,现在没有了这个成员变量,类shape_novirtual应该字节大小为0,但事实上C+编译器不允许对象为零长度。试想一个长度为0的对象在内存中怎么存放?怎么获取它的地址?为了避免这种情况,C+强制给这种类插入一个缺省成员,长度为1。如果有自定义的变量,变量将取代这个缺省成员。 三 虚函数

16、表VTABLE 动态联编过程跟我们猜测的大致相同。编译器在执行过程中遇到virtual关键字的时候,将自动安装动态联编需要的机制,首先为这些包含virtual函数的类(注意不是类的实例)-即使是祖先类包含虚函数而本身没有-建立一张虚拟函数表VTABLE。在这些虚拟函数表中,编译器将依次按照函数声明次序放置类的特定虚函数的地址。同时在每个带有虚函数的类中放置一个称之为vpointer的指针,简称vptr,这个指针指向这个类的VTABLE。 关于虚拟函数表,有几点必须声明清楚: 1. 每一个类别只能有一个虚拟函数表,如果该类没有虚拟函数,则不存在虚拟函数表。 2. C+编译时候编译器会在含有虚函数

17、的类中加上一个指向虚拟函数表的指针vptr。 3. 从一个类别诞生的每一个对象,将获取该类别中的vptr指针,这个指针同样指向类的VTABLE。 因此类、对象、VTABLE的层次结构可以用下图表示。其中X类和Y类的对象的指针 都指向了X,Y的虚拟函数表,同时X,Y类自身也包含了指向虚拟函数的指针。为了方便问题说明,我们将2.cpp例子进行扩展,扩展程序如下。 /4.cpp15. #include <iostream.h >16. class shape17.   public:18.     virtual void dr

18、aw()cout<<"shape:draw()"<<endl;19.     virtual void area()cout<<"shape:area()"<<endl;20.     void fun()draw();area();21. ;22. class circle:public shape23.   public:24.     void draw()cou

19、t<<"circle:draw()"<<endl;25.     void adjust()cout<<"circle:adjust()"<<endl;26. ;27. main()28.     shape oneshape;29.     oneshape.fun();30.     31.     cir

20、cle   circleshape;32.     shape&   baseshape=circleshape;33.     baseshape.fun();34. 编译器在编译上面这段代码的时候将为这shape和circle两个对象分别建立一个VTABLE表,这些表依次填充派生类对象和基类对象中声明的所有的虚函数地址。如果派生类本身没有重新定义基类的虚函数,那么填充的就是基类的虚函数地址。这样一旦如果函数调用一个派生类不存在的方法时候能够自动调用基类方法。然后编

21、译器在每个类中放置一个vptr,一般置于对象的起始位置,继而在对象的构造函数中将vptr初始化为本类的VTABLE的地址。整个结果布局如下。图一图一中的rectangle的VTABLE中的area() 和triangle的VTABLE的adjust()都是填充的基类的虚函数地址。 C+ 编译程序时候按下面的步骤进行工作:为各类建立虚拟函数表,如果没有虚函数则不建立。暂时不连接虚函数,而是将各个虚函数的地址放入虚拟函数表中。直接连接各静态函数。 这些工作做完之后,模块图如图二: 图二执行时候,诞生了oneshape和circleshape两个对象,oneshape对象的vptr指针指向shape

22、的VTABLE,circleshape对象的vptr指针指向circleshape的VTABLE,在执行oneshape.fun()的时候,fun函数的this指针指向了oneshape对象,进入fun()之后程序继续执行this->draw(),由于this指向oneshape对象,oneshape的vptr又指向shape类的VTABLE,这样就从VTABLE中得到需要绑定的函数的地址,并连接起来。同样,this-> area()也经由oneshape对象而连接到相应的函数上,如图三。 图三现在我们执行baseshape.fun()函数。 circle  

23、circleshape;shape&   baseshape=circleshape;baseshape.fun();函数进入fun函数之后,函数的this指针将指向basefun对象,另一方面basefun指向一个circleshape,因此this指针指向的实际上为circleshape对象,而circleshape的vptr指针指向circle类的虚拟函数表,这样编译器将从虚拟表中取出circle:draw()和circle:area()的地址,进行连接。因为circle本身没有重新定义area()方法,因此编译器使用shape的area()方法。如图四。图四

24、遵循上面的思路,基于基类的指针总能找到正确的子类对象的实现。但是象上面的 this->draw是怎么编译的呢。 四 编译内幕在上面的程序中,this指针不同,从而连接到不同的fun函数。那么C+如何编译这些指令呢。道理在于:所有的基类的派生类的虚拟函数表的顺序与基类的顺序是一样的,对于基类中不存在方法再按照声明次序进行排放。这样不管是shape还是circle或者从shape又继承出来的其余的类它们的虚拟函数表的第一项总是draw函数的地址,然后是area的地址。对于circle类,下面的才是adjust的地址。因此不管对于shape还是circle,this->draw总是编译成 call this->VTABLE0; this->area()总是翻译成 call this->VTABLE1; 程序到真正运行时候将会发现this的真正指向的对象,如果是shape,则调用shape->VTABLE0,如果是circle,则调用circle->VTABLE1,如图五。 图五请看下面的这个例子。35. #include 36. class shape37.   public:38.  

温馨提示

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

评论

0/150

提交评论