Chinaunix首页 | 论坛 | 博客
  • 博客访问: 42608
  • 博文数量: 8
  • 博客积分: 165
  • 博客等级: 入伍新兵
  • 技术积分: 96
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-21 16:54
文章分类

全部博文(8)

文章存档

2014年(1)

2013年(1)

2012年(1)

2011年(5)

分类: C/C++

2011-12-12 21:55:49

首先我用下面这一段代码来测试:
  1. #include <iostream>
  2. using namespace std;

  3. class TheBase
  4. {
  5. public:
  6.     TheBase();
  7.     TheBase(int);
  8.     virtual void PrintInfo();
  9.     virtual void PrintItself();
  10.     virtual void PrintTest();
  11. private:
  12.     int m_mem;
  13. };

  14. TheBase::TheBase()
  15. {
  16.     m_mem = 0;
  17. }
  18. TheBase::TheBase(int value)
  19. {
  20.     m_mem = value;
  21. }
  22. void TheBase::PrintInfo()
  23. {
  24.     cout<<"Hello, you have Enter the TheBase::PrintInfo function"<<endl;
  25. }
  26. void TheBase::PrintItself()
  27. {
  28.     cout<<"I am TheBase class"<<endl;
  29. }
  30. void TheBase::PrintTest()
  31. {
  32.     cout<<"Hello, you have Enter the TheBase::PrintTest function"<<endl;
  33. }
  34. //用一个类来继承
  35. class TheSon : public TheBase
  36. {
  37. public:
  38.     TheSon();
  39.     TheSon(int);
  40.     void PrintInfo();
  41.     void PrintItself();
  42. };

  43. TheSon::TheSon()
  44. {
  45. }
  46. TheSon::TheSon(int value):TheBase(value)
  47. {
  48. }
  49. void TheSon::PrintInfo()
  50. {
  51.     cout<<"Hello, you have Enter the TheSon::PrintInfo function"<<endl;
  52. }

  53. void TheSon::PrintItself()
  54. {
  55.     cout<<"I am TheSon class"<<endl;
  56. }


  57. int main()
  58. {
  59.     TheBase* test = new TheSon(10);
  60.     test->PrintInfo();
  61.     test->PrintItself();
  62.     test->PrintTest();
  63.     delete test;
  64.     return 0;
  65. }

相信大家很容易看懂这段代码了,运行结果就可想而知了。但是我们有没有问过问什么会这样呢?虚函数表现出来的动态链编是怎样实现的呢?

对于这些疑问,我现在慢慢分析。

首先就是要确定我们在用test调用函数的时候,到底做了什么事。要知道这些,我通过了反汇编看了一些,同时为了便于分析,我采用的debug版本的。

 

  1. mov eax, [ebp+var_20]; 将类指针传给eax
  2. mov [ebp+var_14], eax
  3. mov [ebp+var_4], 0FFFFFFFFh
  4. mov ecx, [ebp+var_14]
  5. mov [ebp+var_10], ecx
  6. mov edx, [ebp+var_10]
  7. mov eax, [edx];取得类中的虚函数表的起始地址
  8. mov esi, esp
  9. mov ecx, [ebp+var_10]
  10. call dword ptr [eax];调用虚函数表中的第一个函数

根据注释相信大家就能够分析出最后一条call dword ptr [eax]就是调用虚函数表中的第一个函数了,很明显就是PrintInfo,可虚函数表是怎样的呢?我们来看一下子。

.rdata:004320C3                 db    0
.rdata:004320C4 ; const TheSon::`vftable'
.rdata:004320C4 @ dd offset
.rdata:004320C4                                         ; DATA XREF: TheSon::TheSon(void)+28o
.rdata:004320C4                                         ; TheSon::TheSon(int)+2Co
.rdata:004320C4                                         ; TheSon::PrintInfo(void)
.rdata:004320C8                 dd offset ; TheSon::PrintItself(void)
.rdata:004320CC                 dd offset ; TheBase::PrintTest(void)

上面对应的就是TheSon类的虚函数表,相信知道命名粉碎的人都能够看懂上面的代表的含义。不过不了解的话也不要紧,不会有影响我们的理解。大家只需要知道第一个是函数的名字,接着跟的是这个函数所在的类名,后面的是参数规则,在这里就不多说了。

很明显第一个函数是PrintInfo,第二个函数是PrintItself,同时这两个函数所在的类都是TheSon。我们仔细看第三个函数,他的名字是PrintTest,但是所属的类是TheBase,并不是TheSon.可想而知即便我们采用TheSon的对象来调用的话也是调用TheBase中的函数PrintTest。造成这个的原因是:我们在TheSon类中没有管父类中的PrintTest函数,导致编译器默认为没有了虚函数的特性了,也就丧失了虚函数的特性了。

弄清楚了虚函数是通过虚函数表来实现的,那么虚函数是怎样建立的,在什么时候被建立的呢?

要弄清楚这,还得从new创建一个TheSon对象开始说起:

  1. push 8 ; unsigned int 传递的是给new函数的参数,大小是8,为什么?虚函数表地址4字节 + 一个int类型的变量 = 8
  2. call ??2@YAPAXI@Z ; operator new(uint)  调用new来分配空间,当然返回值在eax中。
      ;按照程序的执行,就要将这个分配的空间交给TheSon类来进行一些初始化了,下面的代码就是干这个事。
    1. push 0Ah   ;我们传递个TheSon构造函数的参数是10,所以这里是0Ah
    2. mov ecx, [ebp+var_18];将类的this指针给ecx,其实就是我们前面分配的空间的地址。
    3. call j_??0TheSon@@QAE@H@Z ; TheSon::TheSon(int) 调用TheSon的构造函数。

    进入到类的构造函数中:熟悉C++的人知道,首先就要进行父类的初始化,这个过程是通过调用父类的构造函数实现的。的确代码就是这样执行的。

    1. push eax   ;eax保存的就是我们传递的参数10,现在我们要传递给父类,因为变量在父类中。
    2. mov ecx, [ebp+var_4]  ;这个过程是讲this指针交给ecx,当然现在这个this指针是子类的。其实对于汇编来说
    3.                       ;根本就没有这个说法
    4. call j_??0TheBase@@QAE@H@Z ; TheBase::TheBase(int) ;果然,调用了父类的构造函数,参数是通过压栈传入的。
    现在就要进入父类的构造函数了。
    1. mov [ebp+var_4], ecx   ;将this指针交个了一个局部变量
    2. mov eax, [ebp+var_4]   ;又交给了eax
    3. mov dword ptr [eax], offset ??_7TheBase@@6B@ ; const TheBase::`vftable'  ;现在就要填充虚函数表的地址了。
    4. mov ecx, [ebp+var_4]   ;又将this交给了eax,用于返回。

    当然上面填充的虚函数表是父类的,那么是怎样的呢?

    .rdata:0043201C ; const TheBase::`vftable'
    .rdata:0043201C @ dd offset
    .rdata:0043201C                                         ; DATA XREF: TheBase::TheBase(void)+20o
    .rdata:0043201C                                         ; TheBase::TheBase(int)+20o
    .rdata:0043201C                                         ; TheBase::PrintInfo(void)
    .rdata:00432020                 dd offset ; TheBase::PrintItself(void)
    .rdata:00432024                 dd offset ; TheBase::PrintTest(void)

    相信大家有前面的基础现在看这个表不是很困难。但是将父类的表交给了this指针所指的对象,而this指针有没有类型之分,同时this指针也返回给了子类,那么子类的虚函数表且不是和父类的一样吗?

    肯定不一样了,因为如果是一样,那么运行的结果就不是我们所看到的了,那是怎样处理的呢?

     

    1. mov ecx, [ebp+var_4]
    2. call j_??0TheBase@@QAE@H@Z ; TheBase::TheBase(int)  ;跟前面的一样,调用父类构造函数,在这里写出来是
    3.                            ;为了让大家更好的理解。
    4. mov ecx, [ebp+var_4]    ;这里将this指针交给了ecx
    5. mov dword ptr [ecx], offset ??_7TheSon@@6B@ ; const TheSon::`vftable'  ;注意在这里,又进行了虚函数表的
    6. ;初始化,很显然,就是子类的虚函数表的初始化了,那么子类的虚函数是怎样的呢?前面我已经给出啦。

    到此,我们就明白了C++中到底是怎样实现多态的了,关键在于一张虚函数表。通过上面的分析,我们应该很清楚的看出了虚函数表的建立过程。

    呵呵,到这里就结束了,以上只是我的一些浅薄间接,有错误的还请高手指正。

    阅读(2414) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~