引言

最近在找工作,投了一些c/c++开发工程师,几乎每一个面试官都会问我虚函数的问题,而我面试快一个月了,八股文还是没怎么背,一直在学习Linux驱动开发,这几天我狠下心来,花一天的时间好好整理了以下虚函数的知识点,做个总结,以至于后面再被面试官问到不会一脸懵逼。

虚函数的简介

c++中的三大核心机制是封装、继承、多态。多态分为静态多态和动态多态,静态多态是指函数重载和运算符重载,动态多态的体现则是虚函数。c++中实现虚函数功能的方式是做虚函数表(Virtual Table,VTBL)。在编译阶段,编译器会为每一个有虚函数的类产生一张虚表,每一个虚函数在表表中,虚表的每一项为虚函数的函数指针,这样,当类对象被定义时,对象中就有一个指向虚表的指针,这个指针就是vptr,当对象调用虚函数时,通过vptr找到虚表,再通过虚表找到虚函数的地址,这样就实现了动态绑定。

如何识别虚表

  1. 编写一个简单的例子,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    #include <iostream>

    class base
    {
    public:
    base(std::string name)
    {
    this->member = 0;
    this->member2 = 0;
    std::cout << "base constructor " << name << std::endl;
    }
    ~base()
    {
    std::cout << "base destructor " << std::endl;
    }
    virtual void func0(std::string str)
    {
    std::cout << "base func0 " << str << std::endl;
    }
    int base0_private_func(int a, int b)
    {
    std::cout << "base private_func " << a + b << std::endl;
    return 0;
    }
    int member;
    long long member2;
    };

    class base_fake
    {
    public:
    base_fake(std::string name)
    {
    this->member = 1;
    std::cout << "base_fake constructor " << name << std::endl;
    }
    ~base_fake()
    {
    std::cout << "base_fake destructor " << std::endl;
    }
    virtual void func0_fake(std::string str)
    {
    std::cout << "base_fake func0_fake " << str << std::endl;
    }
    int member;
    };

    class letter : public base
    {
    public:
    letter(std::string name) : base(name)
    {
    this->member = 11;
    std::cout << "letter constructor " << name << std::endl;
    }
    ~letter()
    {
    std::cout << "letter destructor " << std::endl;
    }
    virtual void func0(std::string str)
    {
    std::cout << "letter func0 " << str << std::endl;
    }
    virtual void func1(std::string str)
    {
    std::cout << "letter func1 " << str << std::endl;
    }
    int member = 0;
    };

    class number : public base
    {
    public:
    number(std::string name) : base(name)
    {
    this->member = 22;
    std::cout << "number constructor " << name << std::endl;
    }
    ~number()
    {
    std::cout << "number destructor " << std::endl;
    }
    virtual void func0(std::string str)
    {
    std::cout << "number func0 " << str << std::endl;
    }
    virtual void func2(std::string str)
    {
    std::cout << "number func2 " << str << std::endl;
    }
    int member;
    };

    class misc : public letter, public number
    {
    public:
    misc(std::string name) : letter(name), number(name)
    {
    this->member = 1122;
    std::cout << "misc constructor " << name << std::endl;
    }
    ~misc()
    {
    std::cout << "misc destructor " << std::endl;
    }
    virtual void func0(std::string str)
    {
    std::cout << "misc func0 " << str << std::endl;
    }
    virtual void func1(std::string str)
    {
    std::cout << "misc func1 " << str << std::endl;
    }
    virtual void func2(std::string str)
    {
    std::cout << "misc func2 " << str << std::endl;
    }
    void private_func(int a, int b)
    {
    std::cout << "misc private_func " << a + b << std::endl;
    return;
    }
    int member;
    };

    class all_base : public base, public base_fake
    {
    public:
    all_base(std::string name) : base(name), base_fake(name)
    {
    std::cout << "all_base constructor " << name << std::endl;
    }
    ~all_base()
    {
    std::cout << "all_base destructor" << std::endl;
    }
    virtual void func0(std::string str)
    {
    std::cout << "all_base func0 " << str << std::endl;
    }
    virtual void func3(std::string str)
    {
    std::cout << "all_base func3 " << str << std::endl;
    }
    };

    int main(void)
    {
    std::cout << "----------------" << std::endl;
    all_base a("all_base");
    a.func0("all_base");
    a.func3("all_base");
    std::cout << "----------------" << std::endl;
    base b("base");
    b.func0("all_base");
    std::cout << "----------------" << std::endl;
    misc m("misc");
    m.func0("misc");
    m.func1("misc");
    m.func2("misc");
    std::cout << "----------------" << std::endl;
    return 0;
    }

    output:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    ----------------
    base constructor all_base
    base_fake constructor all_base
    all_base constructor all_base
    all_base func0 all_base
    all_base func3 all_base
    ----------------
    base constructor base
    base func0 all_base
    ----------------
    base constructor misc
    letter constructor misc
    base constructor misc
    number constructor misc
    misc constructor misc
    misc func0 misc
    misc func1 misc
    misc func2 misc
    ----------------
    misc destructor
    number destructor
    base destructor
    letter destructor
    base destructor
    base destructor
    all_base destructor
    base_fake destructor
    base destructor
  2. 代码中类的继承关系如图1所示:

    1

  3. 编译上述代码,用ghidra打开可执行文件,反编译分析该文件,分析程序流程

    1. 多重继承

      • 按照程序执行流程,首先执行的是all_base类的构造函数,在all_base构造函数中调用了base和base_fake的构造函数,所以先分析base和base_fake的构造函数,如图2所示:

        2

        如图所示,在all_base类的构造函数中,先调用了base的构造函数,然后调用了base_fake的构造函数,最后初始化all_base类的VTBL和成员变量,这符合c++语法的构造顺序

      • 先查看base的构造函数,如图3所示:

        base constructor

        在图3中1处上一条汇编语句将this指针传入rax,然后初始化base类VTBL,2和3分别初始化了成员变量

      • 再查看base_fake的构造函数,如图4所示:

        base_fake constructor

        在图4中1处汇编语句将this指针传入rax,然后初始化base_fake类VTBL,2初始化了成员变量

        根据汇编语句和构造顺序可以得出这3个类的内存布局如下所示:

        memory

    2. 菱形继承

      • 按照后续misc部分的执行流程,首先执行的是misc的构造函数,在misc构造函数中调用了letter和number的构造函数,在letter和number的构造函数中分别调用了base类构造函数

        misc constructor

        如图6所示,在misc类的构造函数中,先调用了letter的构造函数,然后调用了number的构造函数,最后初始化misc类的VTBL和成员变量,这符合c++语法的构造顺序

      • 先查看letter的构造函数,如图7所示:

        letter constructor

        在图7中1处上一条汇编语句将this指针传入rax,然后2和3分别初始化了letter类VTBL和成员变量

      • 再查看number的构造函数,如图8所示:

        number constructor

        在图8中1处上一条汇编语句将this指针传入rax,然后2和3分别初始化了number类VTBL和成员变量

      • 查看misc在执行letter和number的构造函数时后面的部分,如图9所示:

        misc constructor

        根据汇编语句和构造顺序可以得出这4个类的内存布局如下所示:

        memory

      上述结构在gcc编译器中有一个比较让人诧异的地方,在misc中定义了int变量时,在后面不会有内存对齐的4字节,如果将int变量修改为long long变量,大小增加的为8字节,会增加内存对齐的4字节,如图11所示:

      memory

注意

根据上述分析,基类成员变量在内存中重复出现了两次,但按照c++虚继承语法规则,先构造基类并且只构造一次,查看资料了解编译器给构造函数多传入了一个参数,用来表示是否调用虚基类的构造,但是我在逆向的过程中并没有发现传入的参数

最后的菱形继承关系的内存布局和c++继承语法规则相冲突,个人一直无法理解,可能存在一些问题,文章仅供参考!

学习过程中还有很多不足,还望朋友们指正!