c – 虚拟表和_vptr存储方案

有人可以解释这个不同类的虚拟表是如何存储在内存中的吗?当我们使用指针调用函数时,他们如何使用地址位置调用函数?我们可以使用类指针获取这些虚拟表内存分配大小吗?我想看看一个类的虚拟表使用了多少内存块.我怎么能看到它?
class Base
{
public:
    FunctionPointer *__vptr;
    virtual void function1() {};
    virtual void function2() {};
};

class D1: public Base
{
public:
    virtual void function1() {};
};

class D2: public Base
{
public:
    virtual void function2() {};
};
int main()
{
    D1 d1;
    Base *dPtr = &d1;
    dPtr->function1();
}

谢谢!提前

解决方法

要记住的第一点是免责声明:标准实际上并未保证这一点.该标准说明代码需要看起来是什么以及它应该如何工作,但实际上并没有确切地指定编译器需要如何实现这一点.

也就是说,基本上所有C编译器在这方面的工作都非常相似.

所以,让我们从非虚函数开始.它们分为两类:静态和非静态.

两者中较简单的是静态成员函数.静态成员函数几乎就像一个全局函数,它是类的朋友,除了它还需要类的名称作为函数名的前缀.

非静态成员函数稍微复杂一些.它们仍然是直接调用的普通函数 – 但它们会传递一个隐藏指针,指向调用它们的对象的实例.在函数内部,您可以使用关键字this来引用该实例数据.因此,当你调用类似a.func(b);之类的东西时,生成代码非常类似于你为func(a,b)获得的代码;

现在让我们考虑虚函数.这是我们进入vtable和vtable指针的地方.我们有足够的间接性,可能最好绘制一些图表,看看它是如何布局的.这几乎是最简单的情况:一个具有两个虚函数的类的一个实例:

因此,该对象包含其数据和指向vtable的指针. vtable包含指向该类定义的每个虚函数的指针.然而,为什么我们需要这么多的间接,可能不会立即显而易见.为了理解这一点,让我们看一下下一个(稍微有点)更复杂的情况:该类的两个实例:

请注意该类的每个实例如何拥有自己的数据,但它们共享相同的vtable和相同的代码 – 如果我们有更多的实例,它们仍然会在同一个类的所有实例中共享一个vtable.

现在,让我们考虑推导/继承.例如,让我们将现有类重命名为“Base”,并添加派生类.由于我感觉富有想象力,我将它命名为“Derived”.如上所述,基类定义了两个虚函数.派生类会覆盖其中一个(但不是其他):

当然,我们可以将两者结合起来,具有每个基类和/或派生类的多个实例:

现在让我们更详细地研究一下.有关派生的有趣之处在于,我们可以将派生类的对象的指针/引用传递给编写的函数,以接收对基类的指针/引用,它仍然有效 – 但是如果调用函数,你得到的是实际类的版本,而不是基类.那么,这是如何工作的?我们如何将派生类的实例视为基类的实例,并且仍然可以工作?为此,每个派生对象都有一个“基类子对象”.例如,让我们考虑这样的代码

struct simple_base { 
    int a;
};

struct simple_derived : public simple_base {
    int b;
};

在这种情况下,当您创建simple_derived的实例时,您将获得一个包含两个整数的对象:a和b. a(基类部分)位于内存中对象的开头,b(派生类部分)紧随其后.因此,如果将对象的地址传递给期望基类实例的函数,它将使用基类中存在的部分,编译器将这些部分置于对象中的相同偏移处. d在基类的对象中,因此函数可以操作它们,甚至不知道它正在处理派生类的对象.同样,如果你调用一个虚函数,它需要知道的是vtable指针的位置.就它而言,Base :: func1之类的东西基本上只意味着它遵循vtable指针,然后使用指向某个指定偏移处的函数的指针(例如,第四个函数指针).

至少现在,我将忽略多重继承.它为图片增加了相当多的复杂性(特别是当涉及虚拟继承时)并且你根本没有提到它,所以我怀疑你真的在乎.

至于访问任何这些,或者以简单地调用函数之外的任何方式使用:你可能能够为特定的编译器提供一些东西 – 但是不要指望它可以移植.虽然像调试器这样的东西经常需要查看这些东西,但所涉及的代码往往非常脆弱且特定于编译器.

相关文章

/** C+⬑ * 默认成员函数 原来C++类中,有6个默认成员函数: 构造函数 析构函数 拷贝...
#pragma once // 1. 设计一个不能被拷贝的类/* 解析:拷贝只会放生在两个场景中:拷贝构造函数以及赋值运...
C类型转换 C语言:显式和隐式类型转换 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译...
//异常的概念/*抛出异常后必须要捕获,否则终止程序(到最外层后会交给main管理,main的行为就是终止) try...
#pragma once /*Smart pointer 智能指针;灵巧指针 智能指针三大件//1.RAII//2.像指针一样使用//3.拷贝问...
目录<future>future模板类成员函数:promise类promise的使用例程:packaged_task模板类例程...