C++对象模型探索--03虚函数

虚函数表指针位置分析

有虚函数,这个类会产生一个虚函数表
类对象有一个指针,指针(vptr)会指向这个虚函数表的开始地址

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <iostream>

class A{
public:
    int m_val;
    virtual void test() {} 
};

int main(int argc, char **argv)
{
    A obj;
    auto len = sizeof(obj);
    std::cout << "sizeof(obj):" << len << std::endl;

    char *p1 = reinterpret_cast<char *>(&obj);
    char *p2 = reinterpret_cast<char *>(&obj.m_val);

    if(p1 == p2) 
    {
        std::cout << "vptr in obj end" << std::endl;
    }
    else
    {
        std::cout << "vptr in obj start" << std::endl;
    }

    return 0;
}

输出为:

sizeof(obj):16
vptr in obj start

obj
  |---->|--------------------|
        |          vptr      |
        |--------------------|
        |        m_val       |
        |--------------------|

继承关系作用下虚函数的手工调用

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <iostream>

class Base
{
public:
    virtual void f()
    {
        std::cout << "Base::f()" << std::endl;
    }

    virtual void g()
    {
        std::cout << "Base::g()" << std::endl;
    }

    virtual void h()
    {
        std::cout << "Base::h()" << std::endl;
    }
};

class Derive : public Base
{
public:
    virtual void g()
    {
        std::cout << "Derive::g()" << std::endl;
    }
};

int main(int argc, char **argv)
{
    std::cout << "sizeof(Base):" << sizeof(Base) << std::endl;
    std::cout << "sizeof(Derive):" << sizeof(Derive) << std::endl;
    std::cout << "--------------------------------" << std::endl;

    Derive *d = new Derive();      // 派生类指针
    long *pvptr = (long *)d;       // 指向对象的指针d转换成long *类型
    long *vptr = (long *)(*pvptr); // (*pvptr)表示pvptr指向的对象,也就是Derive本身,Derive对象是8字节,代表的是虚函数表指针

    for (int i = 0; i < 3; ++i)
    {
        printf("Derive vptr[%d] = %p\n", i, vptr[i]);
    }
    std::cout << std::endl;

    using func = void (*)();

    func df = (func)vptr[0];
    func dg = (func)vptr[1];
    func dh = (func)vptr[2];

    df();
    dg();
    dh();
    std::cout << "--------------------------------" << std::endl;

    Base *b = new Base();
    long *pvptrb = (long *)b;
    long *vptrb = (long *)(*pvptrb);
    for (int i = 0; i < 3; ++i)
    {
        printf("Base vptr[%d] = %p\n", i, vptrb[i]);
    }
    std::cout << std::endl;

    func bf = (func)vptrb[0];
    func bg = (func)vptrb[1];
    func bh = (func)vptrb[2];
    bf();
    bg();
    bh();

    return 0;
}

运行结果为:

sizeof(Base):8
sizeof(Derive):8
--------------------------------
Derive vptr[0] = 0x5555555554fa
Derive vptr[1] = 0x5555555555b4
Derive vptr[2] = 0x555555555576

Base::f()
Derive::g()
Base::h()
--------------------------------
Base vptr[0] = 0x5555555554fa
Base vptr[1] = 0x555555555538
Base vptr[2] = 0x555555555576

Base::f()
Base::g()
Base::h()


请添加图片描述

虚函数表分析

  1. 一个类只有包虚函数才会存在虚函数表,同属于一个类的对象共享虚函数表,但是有各自的vptr(虚函数表指针),所指向的地址(虚函数表首地址)是相同的
  2. 父类中有虚函数就等于子类中有虚函数,换而言之,父类中有虚函数表,子类中则肯定有虚函数表,因为子类是继承自父类的。只要在父类中是虚函数,子类中即使不写virtual也是虚函数,但不管是父类还是子类都只会有一个虚函数表
  3. 如果子类中完全没有虚函数,则认为子类的虚函数表和父类的虚函数表类容相同。仅仅是类容相同,这2个表在内存中的位置不同,是类容相同的2张表。

多重继承虚函数表分析

一个对象,如果它的类有多个基类,则有多个虚函数表指针,在继承中,对应各个基类的vptr按继承顺序依次放在类的内存空间中,且子类与第一个基类共用一个vptr(第二个基类有自己的vptr)

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <iostream>

class Base1
{
public:
    virtual void f()
    {
        std::cout << "Base1::f()" << std::endl;
    }

    virtual void g()
    {
        std::cout << "Base1::g()" << std::endl;
    }
};

class Base2
{
public:
    virtual void h()
    {
        std::cout << "Base2::h()" << std::endl;
    }

    virtual void i()
    {
        std::cout << "Base2::i()" << std::endl;
    }
};

class Derive : public Base1, public Base2
{
public:
    virtual void f()
    {
        std::cout << "Derive::f()" << std::endl;
    }

    virtual void i()
    {
        std::cout << "Derive::i()" << std::endl;
    }

    virtual void mh()
    {
        std::cout << "Derive::mh()" << std::endl;
    }

    virtual void mi()
    {
        std::cout << "Derive::mi()" << std::endl;
    }

    virtual void mj()
    {
        std::cout << "Derive::mj()" << std::endl;
    }
};

int main(int argc, char **argv)
{
    std::cout << "sizeof(Base1):" << sizeof(Base1) << std::endl;
    std::cout << "sizeof(Base2):" << sizeof(Base2) << std::endl;
    std::cout << "sizeof(Derive):" << sizeof(Derive) << std::endl;
    std::cout << "--------------------------------" << std::endl;

    Derive ins;
    Base1 &b1 = ins;
    Base2 &b2 = ins;

    Derive &d = ins;

    using func = void (*)();

    long *pderive1 = (long *)(&ins);
    long *vptr1 = (long *)(*pderive1); // 第一个虚函数表指针

    long *pderive2 = pderive1 + 1;     // 跳过8byte
    long *vptr2 = (long *)(*pderive2); // 取第二个虚函数表指针

    func f1 = (func)vptr1[0];
    func f2 = (func)vptr1[1];
    func f3 = (func)vptr1[2];
    func f4 = (func)vptr1[3];
    func f5 = (func)vptr1[4];
    func f6= (func)vptr1[5];

    func ff1 = (func)vptr2[0];
    func ff2 = (func)vptr2[1];

    b1.f();
    b2.i();
    d.f();
    d.i();
    d.mh();
    d.g();

    std::cout << "--------------------------------" << std::endl;

    f1();
    f2();
    f3();
    f4();
    f5();
    f6();
    std::cout << "--------------------------------" << std::endl;

    ff1();
    ff2();

    return 0;
}

输出如下

sizeof(Base1):8
sizeof(Base2):8
sizeof(Derive):16
--------------------------------
Derive::f()
Derive::i()
Derive::f()
Derive::i()
Derive::mh()
Base1::g()
--------------------------------
Derive::f()
Base1::g()
Derive::i()
Derive::mh()
Derive::mi()
Derive::mj()
--------------------------------
Base2::h()
Derive::i()

linux 下运行g++ -fdump-lang-class main.cpp(g++ -fdump-tree-gimple main.cpp生成gimple文件)生成a-main.cpp.001l.class文件,从里面可以找到

Vtable for Base1
Base1::_ZTV5Base1: 4 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI5Base1)
16    (int (*)(...))Base1::f
24    (int (*)(...))Base1::g

Class Base1
   size=8 align=8
   base size=8 base align=8
Base1 (0x0x7f67cf7ed1e0) 0 nearly-empty
    vptr=((& Base1::_ZTV5Base1) + 16)
	
	
Vtable for Base2
Base2::_ZTV5Base2: 4 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI5Base2)
16    (int (*)(...))Base2::h
24    (int (*)(...))Base2::i

Class Base2
   size=8 align=8
   base size=8 base align=8
Base2 (0x0x7f67cf7ed6c0) 0 nearly-empty
    vptr=((& Base2::_ZTV5Base2) + 16)
	
Vtable for Derive
Derive::_ZTV6Derive: 12 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI6Derive)
16    (int (*)(...))Derive::f
24    (int (*)(...))Base1::g
32    (int (*)(...))Derive::i
40    (int (*)(...))Derive::mh
48    (int (*)(...))Derive::mi
56    (int (*)(...))Derive::mj
64    (int (*)(...))-8
72    (int (*)(...))(& _ZTI6Derive)
80    (int (*)(...))Base2::h
88    (int (*)(...))Derive::_ZThn8_N6Derive1iEv

Class Derive
   size=16 align=8
   base size=16 base align=8
Derive (0x0x7f67cf806230) 0
    vptr=((& Derive::_ZTV6Derive) + 16)
Base1 (0x0x7f67cf7ed7e0) 0 nearly-empty
      primary-for Derive (0x0x7f67cf806230)
Base2 (0x0x7f67cf7ed840) 8 nearly-empty
      vptr=((& Derive::_ZTV6Derive) + 80)

结论

  • 子类对象ins有2个虚函数表指针vptr1,vptr2
  • 类Derive有2个虚函数表,因为它继承了2个基类
  • 子类和第一个基类共用一个vptr
  • 子类中虚函数覆盖了父类中同盟的虚函数

虚函数之vptr、vtbl创建时机

vprt虚函数表指针什么时候创建?

vptr跟着对象走,所以对象什么时候创建出来,vptr就什么时候创建出来,即运行时创建。实际上对于有虚函数的类,在编译的时候,编译器会往相关的构造函数中增加为vptr赋值的代码,这是在编译期间编译器为构造函数所增加的代码。当程序运行时,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有给对象的vptr赋值的语句,自然这个对象的vptr就被赋值了

虚函数表vtbl时什么时候创建的?

虚函数表(vtbl)是编译器在编译期间就为每个类确定好了对应的虚函数表vtbl的内容,然后也是在编译器编译期间在相应的类构造函数中添加vtbl赋值的代码,当程序运行时,运行到生成类对象代码时,会调用类的构造函数,执行到类的构造函数中的给vtbl赋值的代码,那么这个类对象的vtbl就有值了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

血_影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值