【虚表指针与虚函数关系浅析】

概要

大家应该听过这样如下面试题:动态多态是如何实现的?虚表指针能否继承?虚函数表呢?

本文对调试信息进行分析后得出结论,系统:ubuntu-20.04 64位,编译器: gcc version 13.1.0

普通继承

  • 1.首先有如下继承关系,Son -> Father -> Grandpa,并且Son、Father类均对Grandpa类中的虚函数print进行了重写
#include <iostream>

using namespace std;

class Grandpa
{
public:
    virtual void print()
    {
        cout << "grandpa" << endl;
    }

    int age = 0x12345678;
};

class Father: public Grandpa
{
public:
    virtual void print()
    {
        cout << "father" << endl;
    }
    char ch = 'a';
};

class Son : public Father{
public:
    virtual void print()
    {
        cout << "son" << endl;
    }

    short money = 0x6666;
};

int main(){
    Grandpa gp;
    Father fa;
    Son s;
    
    size_t len_gp = sizeof(gp);
    size_t len_fa = sizeof(fa);
    size_t len_s = sizeof(s);

    return 0;
}

2.编译后使用gdb在最后一行打上断点,运行到断点处在这里插入图片描述

3.查看3个对象分别占用的字节数,可以发现都是16字节
在这里插入图片描述
4.查看三个对象的数据,并在内存中查看其对象的地址
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

小结

可以看到,在普通继承中,无论继承了多少次,都只有一个虚表指针,该虚表指针存放于对象的首地址。注意虚表指针不是直接指向虚表的首地址,而是指向虚表首地址向后偏移16个字节的位置
问题:那为什么虚表指针不直接指向虚表而是有16个字节的偏移?

5.通过类Son的虚表指针减去16个字节的偏移再查看虚表内容,结果如下图
在这里插入图片描述
可以看到, _ZTV3Son是gcc中name mangle,表示类Son的虚表信息,son的虚表中,第一行为空行;第二行
指向的区域是_ZTI3Son,通过x /16bx 查看内存可知_ZTI3Son为类的type info信息;第三行就是Son的虚表指针指向的位置,通过 info symbol 0x555555555298可知,第三行为Son::print()函数的地址。

从以上内存布局可以看出,类Grandpa、Father、Son的虚表中print函数在不同的内存地址。

问题:那么在动态多态时,编译器是如何知道该调用哪一个类的虚表中的函数呢?

虚函数的偏移

首先虚函数的偏移是不变的,下面通过一个例子来说明。

#include <iostream>

using namespace std;

class Grandpa
{
public:
    virtual void print()
    {
        cout << "grandpa" << endl;
    }
    virtual void show()
    {
        cout << "grandpa" << endl;
    }
    int age = 0x12345678;
};

class Father: public Grandpa
{
public:
    virtual void display()
    {
        cout << "father" << endl;
    }

    virtual void print()
    {
        cout << "father" << endl;
    }
    char ch = 'a';
};

class Son : public Father{
public:
    virtual void look(){

    }

    virtual void print()
    {
        cout << "son" << endl;
    }

    short money = 0x6666;
};

int main(){
    Grandpa gp;
    Father fa;
    Son s;

    return 0;
}

打上断点并查看三个对象的虚表内容,结果如下图所示:
在这里插入图片描述

可以看到,依照在代码中虚函数的声明顺序,print()函数的偏移始终为0,show()函数的偏移始终为1,无论是在对象gp、fa,还是s中,偏移顺序始终不变。这样,编译器就可以通过虚函数的声明顺序得到函数名和偏移之间的映射。而指向派生类对象的基类指针ptr,不管指向哪个对象,都可以通过ptr指向的对象首址,来获得虚表指针。这样通过虚表指针进一步指向虚表中的函数,再通过函数名与偏移的映射就能找到正确的虚函数,函数调用可看作如下通用代码(*ptr->vptr[offset])()(注意优先级)。

总结

所以可以看到在Ubuntu环境下,虚表指针并没有继承,虚函数表也没有继承。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值