C++对象模型(10)-- 虚函数2

1、虚函数表、虚函数表指针的创建时机

我们知道虚函数表是属于类的,而虚函数表指针是属于对象的。在编译的时候,编译器会往类的构造函数中插入创建虚函数表指针的代码。同样,在编译期间编译器也为每个类确定好了对应的虚函数表的内容。

虚函数和普通函数一样,它们的地址在编译的时候就已经确定了。

当new一个含虚函数的类对象时,虚函数表、虚函数表指针、虚函数的内存布局是这样的:

2、不通过虚函数表指针的方式调用虚函数

虚函数和普通成员函数一样,地址在编译完成后就确定了。你可以通过虚函数表指针间接地调用虚函数,也可以通过对象直接调用虚函数。

(1)在成员函数中调用虚函数

在成员函数中调用虚函数跟调用普通函数一样,是不通过虚函数表的。

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

class Derive :public Base {
public:
            
    
    void testVirFun() {
        vir_f();
    }


};

int main()
{
    Derive derive;
    derive.testVirFun();

    return 0;
}

这时输出:Base::vir_f(),调用的是父类的虚函数。

在Derive类中添加虚函数vir_f():

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

这时输出:Derive::vir_f(),调用的是子类的虚函数。

(2)清除虚函数表指针后再调用虚函数

我们知道虚函数表指针是在构造函数中创建的,现在假如我们在构造函数中把虚函数表指针去掉,看看这时虚函数能否被调用。

class Base {
public:
    int b_i = 8;
    // 在构造函数中用memset把虚函数表指针去掉
    Base() {
        memset(this, 0, sizeof(Base));
    }

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

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

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

这时,我们在main()函数中加入如下代码:

int main()
{

    Base* pb = new Base();
    

    pb->vir_f();
}

发现运行报错。因为在Base的构造函数中,我们用memset()把虚函数表指针给清除掉了。

现在我们把main()函数修改一下,改成下面这样。

int main()
{
    Base base;
    base.vir_f();
}

发现虚函数vir_f()能够被正确调用。

3、vcall

使用函数指针调用成员虚函数的时候会使用到vcall,通过vcall能从虚函数表中找到被调用的虚函数地址。

我们可以通过代码来验证这个vcall。

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

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

int main()
{
    printf(" Base::vir_f的地址:%p\n", &Base::vir_f);
    printf(" Base::vir_g的地址:%p\n", &Base::vir_g);

    Base* pb = new Base;
    pb->vir_g();

    std::cout << "pause" << std::endl;
    return 0;
}

把断点设在这行:std::cout

从运行结果可以看到,虚函数的地址分别是:00f111c2、00f11104,根打印的地址不同。

把断点设在printf这行,然后再运行,切换到反汇编窗口。

可以看到vcall{0}, (0F1146Ah)和vcall{4}, (0F110D2h)这样的内容。

其中,vcall{0}对应虚函数vir_f(),vcall{4}对应虚函数vir_g()。我们可以这么认为,在调用虚函数时系统先是找到vcall,然后再通过vcall找到真正需要调用的虚函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值