C++虚函数表

Visual Studio调试技巧

我们使用Visual Studio工具来查看C++对象的内存布局,所以需要在当前项目上右键单击选择“属性”后,打开属性页,在配置属性->C/C+±>命令行下的其它选项文本框中配置如下命令:

/d1 reportAllClassLayout

在“调试”中选“输出”,然后在下来框中选“生成”

无虚函数

/*
Base1.cpp
*/
class Base1
{
    public:
        int base1_1;
        int base1_2;

        void base1_fun1() {}
};

内存分布只有base1_1和base1_2,base1_fun1()函数的内存是在调用时动态分配

此种情况下,内存分布为

-->+------------+
   |  base1_1   |
   +------------+
   |  base1_2   |
   +------------+

单个类,没继承

/*
Base1.cpp
*/
class Base1
{
    public:
        int base1_1;
        int base1_2;

        virtual void base1_fun1() {}
        virtual void base1_fun2() {}

};
/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
    Base1* b1 = new Base1();
    b1->base1_fun1();
    return 0;
}

反汇编之后的结果

 b1->base1_fun1();
007219D7  mov         eax,dword ptr [b1]  //把b1地址的值赋值给eax
007219DA  mov         edx,dword ptr [eax] //把eax对应的内存地址上的值赋值给edx,即edx内保存b1所对内存的地址
007219DC  mov         esi,esp  
007219DE  mov         ecx,dword ptr [b1]  //b1对应内存的地址赋值给ecx
007219E1  mov         eax,dword ptr [edx] //edx对应内存的地址,即b1指针赋值给eax
007219E3  call        eax                 //调用eax

总的来说,就是b1的首地址给eax,call eax就是调用base1_fun1()

结合vs2019上的变量监视以及类对象内存结构分析可知,b1的首地址保存的是虚函数表(__vfptr)

__vfptr的类型是void**,因此,指向的是一个数组,该数组的内部是 void * 对象,即

__vfprt-->+--------+
          | void * |
          +--------+
          | void * |
          +--------+

根据上述的代码可知

__vfptr --> +--------------+
            | base1_func1()|
            +--------------+

GDB调试

为了方便,将上面的堆分配改为栈分配

/*
main.cpp
*/
#include "Base1.cpp"
int main()
{
    Base1 b1;
    b1.base1_fun1();
    return 0;
}

首先用g++进行编译

g++ base.h main.cpp -g

然后用gdb进行调试

gdb ./a.out
b main

将断点停在b1.base1_func1()

然后用gdb查看内存变化
在这里插入图片描述

可以看一下ZTV4Base是什么
在这里插入图片描述
objdump -d a.out看一下frame_dummy

0000000000001140 <frame_dummy>:
    1140:       f3 0f 1e fa             endbr64 
    1144:       e9 77 ff ff ff          jmpq   10c0 <register_tm_clones>

有继承,无扩展

class Base1
{
    public:
        int base1_1;
        int base1_2;

        virtual void base1_fun1() {}
        virtual void base1_fun2() {}

};
class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;
};
int main()
{
    Base1* b1 = new Base1();
    b1->base1_fun1();
    return 0;
}
 b1->base1_fun2();
002C19D7  mov         eax,dword ptr [b1]  
002C19DA  mov         edx,dword ptr [eax]  
002C19DC  mov         esi,esp  
002C19DE  mov         ecx,dword ptr [b1]  
002C19E1  mov         eax,dword ptr [edx+4]  
002C19E4  call        eax  

可以看到eax中放的是b1往下偏移4个字节的位置,即eax的值保存的是base2_func2()的地址

__vfprt-->+---------------+
          | base1_fun1() |
          +---------------+ <--eax
          | base2_fun2() |
          +---------------+

利用VS2019的工具,可以输出当前的类结构

class Base1	size(12):
1>	+---
1> 0	| {vfptr}
1> 4	| base1_1
1> 8	| base1_2
1>	+---
1>Base1::$vftable@:
1>	| &Base1_meta
1>	|  0
1> 0	| &Base1::base1_fun1
1> 1	| &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Derive1	size(20):
1>	+---
1> 0	| +--- (base class Base1)
1> 0	| | {vfptr}
1> 4	| | base1_1
1> 8	| | base1_2
1>	| +---
1>12	| derive1_1
1>16	| derive1_2
1>	+---
1>Derive1::$vftable@:
1>	| &Derive1_meta
1>	|  0
1> 0	| &Base1::base1_fun1
1> 1	| &Base1::base1_fun2

重新修改其表现形式
在这里插入图片描述

Derive类覆盖基类的函数

/*
Base1.cpp
*/
class Base1
{
    public:
        int base1_1;
        int base1_2;

        virtual void base1_fun1() {}
        virtual void base1_fun2() {}

};
class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;

    //覆盖基类函数
    virtual void base1_fun1() {}
};
/*
main.cpp
*/
#include "Base1.cpp"

int main()
{
    Derive1* d1 = new Derive1();
    d1->base1_fun1();
    return 0;
}

反汇编

d1->base1_fun1();
00741A5D  mov         eax,dword ptr [d1]  
00741A60  mov         edx,dword ptr [eax]  
00741A62  mov         esi,esp  
00741A64  mov         ecx,dword ptr [d1]  
00741A67  mov         eax,dword ptr [edx]  
00741A69  call        eax  

可以看到call eax就是调用base1_fun1(),而该函数对应的地址是d1变量对应的首地址

通过vs2019生成内存结构如下

class Base1	size(12):
1>	+---
1> 0	| {vfptr}
1> 4	| base1_1
1> 8	| base1_2
1>	+---
1>Base1::$vftable@:
1>	| &Base1_meta
1>	|  0
1> 0	| &Base1::base1_fun1
1> 1	| &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Derive1	size(20):
1>	+---
1> 0	| +--- (base class Base1)
1> 0	| | {vfptr}
1> 4	| | base1_1
1> 8	| | base1_2
1>	| +---
1>12	| derive1_1
1>16	| derive1_2
1>	+---
1>Derive1::$vftable@:
1>	| &Derive1_meta
1>	|  0
1> 0	| &Derive1::base1_fun1
1> 1	| &Base1::base1_fun2
1>Derive1::base1_fun1 this adjustor: 0

重新修改其表现形式
在这里插入图片描述

Derive1新加了虚函数

/*
Base1.cpp
*/
class Base1
{
    public:
        int base1_1;
        int base1_2;

        virtual void base1_fun1() {}
        virtual void base1_fun2() {}

};

class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;

    virtual void derive1_fun1() {}
};
/*
main.cpp
*/
#include "Base1.cpp"

int main()
{
    Derive1* d1 = new Derive1();
    d1->derive1_fun1();
    return 0;
}

反汇编

 d1->derive1_fun1();
00F21AAD  mov         eax,dword ptr [d1]  
00F21AB0  mov         edx,dword ptr [eax]  
00F21AB2  mov         esi,esp  
00F21AB4  mov         ecx,dword ptr [d1]  
00F21AB7  mov         eax,dword ptr [edx+8]  
00F21ABA  call        eax  

可以看到调用的函数处于首地址+8的偏移处,查看其vs2019生成的内存结构

class Base1	size(12):
1>	+---
1> 0	| {vfptr}
1> 4	| base1_1
1> 8	| base1_2
1>	+---
1>Base1::$vftable@:
1>	| &Base1_meta
1>	|  0
1> 0	| &Base1::base1_fun1
1> 1	| &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Derive1	size(20):
1>	+---
1> 0	| +--- (base class Base1)
1> 0	| | {vfptr}
1> 4	| | base1_1
1> 8	| | base1_2
1>	| +---
1>12	| derive1_1
1>16	| derive1_2
1>	+---
1>Derive1::$vftable@:
1>	| &Derive1_meta
1>	|  0
1> 0	| &Base1::base1_fun1
1> 1	| &Base1::base1_fun2
1> 2	| &Derive1::derive1_fun1

重新修改其表现形式
在这里插入图片描述

多继承覆盖

/*
Base1.cpp
*/
class Base1
{
public:
    int base1_1;
    int base1_2;

    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};

class Base2
{
public:
    int base2_1;
    int base2_2;

    virtual void base2_fun1() {}
    virtual void base2_fun2() {}
};

// 多继承
class Derive1 : public Base1, public Base2
{
public:
    int derive1_1;
    int derive1_2;

    // 基类虚函数覆盖
    virtual void base1_fun1() {}
    virtual void base2_fun2() {}

    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"

int main()
{
    Derive1* d1 = new Derive1();
    d1->derive1_fun1();
    return 0;
}

反汇编

d1->derive1_fun1();
008E4936  mov         eax,dword ptr [d1]  
008E4939  mov         edx,dword ptr [eax]  
008E493B  mov         esi,esp  
008E493D  mov         ecx,dword ptr [d1]  
008E4940  mov         eax,dword ptr [edx+8]  
008E4943  call        eax

vs2019生成的内存结构

class Base1	size(12):
1>	+---
1> 0	| {vfptr}
1> 4	| base1_1
1> 8	| base1_2
1>	+---
1>Base1::$vftable@:
1>	| &Base1_meta
1>	|  0
1> 0	| &Base1::base1_fun1
1> 1	| &Base1::base1_fun2
1>Base1::base1_fun1 this adjustor: 0
1>Base1::base1_fun2 this adjustor: 0
1>class Base2	size(12):
1>	+---
1> 0	| {vfptr}
1> 4	| base2_1
1> 8	| base2_2
1>	+---
1>Base2::$vftable@:
1>	| &Base2_meta
1>	|  0
1> 0	| &Base2::base2_fun1
1> 1	| &Base2::base2_fun2
1>Base2::base2_fun1 this adjustor: 0
1>Base2::base2_fun2 this adjustor: 0
1>class Derive1	size(32):
1>	+---
1> 0	| +--- (base class Base1)
1> 0	| | {vfptr}
1> 4	| | base1_1
1> 8	| | base1_2
1>	| +---
1>12	| +--- (base class Base2)
1>12	| | {vfptr}
1>16	| | base2_1
1>20	| | base2_2
1>	| +---
1>24	| derive1_1
1>28	| derive1_2
1>	+---
1>Derive1::$vftable@Base1@:
1>	| &Derive1_meta
1>	|  0
1> 0	| &Derive1::base1_fun1
1> 1	| &Base1::base1_fun2
1> 2	| &Derive1::derive1_fun1
1> 3	| &Derive1::derive1_fun2
1>Derive1::$vftable@Base2@:
1>	| -12
1> 0	| &Base2::base2_fun1
1> 1	| &Derive1::base2_fun2

修改其表现形式如下
在这里插入图片描述

如果基类没有虚函数

/*
Base1.cpp
*/
class Base1
{
public:
    int base1_1;
    int base1_2;
};
 
class Base2
{
public:
    int base2_1;
    int base2_2;
 
    virtual void base2_fun1() {}
    virtual void base2_fun2() {}
};
 
// 多继承
class Derive1 : public Base1, public Base2
{
public:
    int derive1_1;
    int derive1_2;
 
    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"

int main()
{
    Derive1* d1 = new Derive1();
    d1->derive1_fun1();
    return 0;
}

反汇编

d1->derive1_fun1();
00561B63  mov         eax,dword ptr [d1]  
00561B66  mov         edx,dword ptr [eax]  
00561B68  mov         esi,esp  
00561B6A  mov         ecx,dword ptr [d1]  
00561B6D  mov         eax,dword ptr [edx+8]  
00561B70  call        eax

vs2019生成的内存结构

class Base1	size(8):
1>	+---
1> 0	| base1_1
1> 4	| base1_2
1>	+---
1>class Base2	size(12):
1>	+---
1> 0	| {vfptr}
1> 4	| base2_1
1> 8	| base2_2
1>	+---
1>Base2::$vftable@:
1>	| &Base2_meta
1>	|  0
1> 0	| &Base2::base2_fun1
1> 1	| &Base2::base2_fun2
1>Base2::base2_fun1 this adjustor: 0
1>Base2::base2_fun2 this adjustor: 0
1>class Derive1	size(28):
1>	+---
1> 0	| +--- (base class Base2)
1> 0	| | {vfptr}
1> 4	| | base2_1
1> 8	| | base2_2
1>	| +---
1>12	| +--- (base class Base1)
1>12	| | base1_1
1>16	| | base1_2
1>	| +---
1>20	| derive1_1
1>24	| derive1_2
1>	+---
1>Derive1::$vftable@:
1>	| &Derive1_meta
1>	|  0
1> 0	| &Base2::base2_fun1
1> 1	| &Base2::base2_fun2
1> 2	| &Derive1::derive1_fun1
1> 3	| &Derive1::derive1_fun2

修改其表达方式

在这里插入图片描述

这里的虚函数表指针位置是不对的,按照反汇编可知,其内存分布的图形应如下

在这里插入图片描述

两个基类都没有虚函数

/*
Base1.cpp
*/
class Base1
{
public:
    int base1_1;
    int base1_2;
};
 
class Base2
{
public:
    int base2_1;
    int base2_2;
};
 
// 多继承
class Derive1 : public Base1, public Base2
{
public:
    int derive1_1;
    int derive1_2;
 
    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
};
/*
main.cpp
*/
#include "Base1.cpp"

int main()
{
    Derive1* d1 = new Derive1();
    d1->derive1_fun1();
    return 0;
}

反汇编

d1->derive1_fun1();
00791A03  mov         eax,dword ptr [d1]  
00791A06  mov         edx,dword ptr [eax]  
00791A08  mov         esi,esp  
00791A0A  mov         ecx,dword ptr [d1]  
00791A0D  mov         eax,dword ptr [edx]  
00791A0F  call        eax  

vs2019生成的内存结构

class Base1	size(8):
1>	+---
1> 0	| base1_1
1> 4	| base1_2
1>	+---
1>class Base2	size(8):
1>	+---
1> 0	| base2_1
1> 4	| base2_2
1>	+---
1>class Derive1	size(28):
1>	+---
1> 0	| {vfptr}
1> 4	| +--- (base class Base1)
1> 4	| | base1_1
1> 8	| | base1_2
1>	| +---
1>12	| +--- (base class Base2)
1>12	| | base2_1
1>16	| | base2_2
1>	| +---
1>20	| derive1_1
1>24	| derive1_2
1>	+---
1>Derive1::$vftable@:
1>	| &Derive1_meta
1>	|  0
1> 0	| &Derive1::derive1_fun1
1> 1	| &Derive1::derive1_fun2

修改表现方式如下
在这里插入图片描述

虚函数表指针规律

哪个类有虚函数表,哪个类就更靠前

C++利用虚函数表实现多态

根据上面的内存分析可知,任何一个类都是把成员变量按照顺序排列,但是实现的虚函数是按照虚函数表在其他的位置排列,在类的头部用虚函数表指针引导。

因此任何类的继承都可以通过这种方式实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值