虚表和虚表指针

虚表和虚表指针

  编译器:VS2015

 

0x01 基础概念

  首先还是简单重复一下基础概念。

  C++的多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。

   1、多态性

    指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。

    a、编译时多态性:通过重载函数实现

    b、运行时多态性:通过虚函数实现。

 

    2、虚函数

    虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖(Override)

 

     3.虚函数表

  编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4(x64下是N*8)的大小。

  派生类的虚函数表存放重写的虚函数,当基类的指针指向派生类的对象时,调用虚函数时都会根据vptr(虚表指针)来选择虚函数,而基类的虚函数在派生类里已经被改写或者说已经不存在了,所以也就只能调用派生类的虚函数版本了.

 

  4.虚表指针

  虚表指针在类对象中,每个同类对象中都有个一个vptr,指向内存中的vtable,所有同类对象,共享一个vtable,但是每个对象都自带一个vptr指向这个vtable,否则调用虚函数的时候会找不到正确的函数入口,(后面将会讲明)虚表指针是对象的第一个数据成员。

 

0x02 虚表和虚表指针的反汇编分析

   我是直接通过VS2015的虚函数的反汇编指令来分析的。

   先看源代码:

  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class CVirtual

{

public:

    virtual int GetNumber()

    {

        return m_nNumber;

        return 0;

    }

    virtual void SetNumber(int nNumber)

    {

        m_nNumber = nNumber;

    }

private:

    int m_nNumber;

};

 

int main()

{

    int a = sizeof(CVirtual);

    CVirtual TheVirtual;

    return 0;

}

  反汇编:

  f11进类对象的构造函数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

CVirtual::CVirtual:

00A316E0 55                   push        ebp 

00A316E1 8B EC                mov         ebp,esp 

00A316E3 81 EC CC 00 00 00    sub         esp,0CCh 

00A316E9 53                   push        ebx 

00A316EA 56                   push        esi 

00A316EB 57                   push        edi 

00A316EC 51                   push        ecx 

00A316ED 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh] 

00A316F3 B9 33 00 00 00       mov         ecx,33h 

00A316F8 B8 CC CC CC CC       mov         eax,0CCCCCCCCh 

00A316FD F3 AB                rep stos    dword ptr es:[edi] 

00A316FF 59                   pop         ecx 

00A31700 89 4D F8             mov         dword ptr [this],ecx 

00A31703 8B 45 F8             mov         eax,dword ptr [this

00A31706 C7 00 34 6B A3 00    mov         dword ptr [eax],offset CVirtual::`vftable' (0A36B34h) 

00A3170C 8B 45 F8             mov         eax,dword ptr [this

00A3170F 5F                   pop         edi 

00A31710 5E                   pop         esi 

00A31711 5B                   pop         ebx 

00A31712 8B E5                mov         esp,ebp 

00A31714 5D                   pop         ebp 

00A31715 C3                   ret 

  先分析构造函数开栈之后的这三句汇编指令:

1

2

3

00E716FF 59                   pop         ecx 

00E71700 89 4D F8             mov         dword ptr [this],ecx 

00E71703 8B 45 F8             mov         eax,dword ptr [this

  第一句 pop         ecx ,类的非静态成员函数调用时,会传入一个隐藏的参数,也就是this指针,这里的pop,也就是还原this指针的值保存在ecx中了,接下来this指针的值又被保存到了eax中。

  接下来一句,就看到关键了:

1

00E71706 C7 00 34 6B E7 00    mov         dword ptr [eax],offset CVirtual::`vftable' (0E76B34h) 

  取出了虚表的首地址,保存到了虚表指针中(这里也可以看出,虚表指针是对象的第一个数据成员,也就是说对象的首地址就是虚表指针的首地址)。

1

00E7170C 8B 45 F8             mov         eax,dword ptr [this

  将对象的首地址保存到eax中,准备作为返回值。

  

  执行过这五句汇编指令后,再来看看局部变量窗口中this指针的值,实在一目了然~:

 

  虚表指针指向地址存储着两个虚函数的地址(注意到两个函数地址结束之后,填充了四个字节的0,我猜测这是虚表的结束标志,就像字符串的结束标志“/o”一样):

 

  根据上面分析的五句汇编指令,可以得出这样的结论:
  虚表(虚表存储在数据段)的地址存放在对象的起始位置,即对象的第一个数据成员就是它的虚表指针,同时注意到,虚表指针的初始化发生在构造函数过程中。

 

 

0x03  派生类中的虚表结构:

 

  1.一般继承(无虚函数覆盖)

                                        

 

    子类虚函数表结构:

          

  

  2.一般继承(有虚函数覆盖)

                             

 

              子类虚函数表结构:

                                     

 

    

    1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

    2)没有被覆盖的函数依旧。

 

  3.多重继承(无虚函数覆盖)

                              

 

 

            

     子类虚函数表结构:

                               

 

 

    

    1)  每个父类都有自己的虚表

    2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

 

    4.多重继承(有虚函数覆盖)

                        

    

     子类虚函数表结构:

      

 

    ,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了:

 

 

1

2

3

4

5

6

7

8

9

10

11

Derive d; 

Base1 *b1 = &d; 

Base2 *b2 = &d; 

Base3 *b3 = &d; 

b1->f(); //Derive::f() 

b2->f(); //Derive::f() 

b3->f(); //Derive::f() 

   

b1->g(); //Base1::g() 

b2->g(); //Base2::g() 

b3->g(); //Base3::g() 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值