有人能解释一下不同类的虚拟表是如何存储在内存中的吗?当我们使用指针调用函数时,他们如何使用地址位置调用函数?我们可以使用类指针获得这些虚拟表内存分配大小吗?我想看看一个虚拟表为一个类使用了多少个内存块。我怎么看得到?
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();
}谢谢!预先
发布于 2017-11-06 04:51:10
首先要记住的是免责声明:所有这些都不是标准所保证的。该标准说明了代码的外观和应该如何工作,但实际上并没有具体说明编译器需要如何实现这一点。
也就是说,基本上所有的C++编译器在这方面的工作都是非常相似的。
那么,让我们从非虚拟函数开始。它们分为两类:静态的和非静态的。
其中比较简单的是静态成员函数。静态成员函数几乎类似于类的friend的全局函数,只不过它还需要类名作为函数名的前缀。
非静态成员函数稍微复杂一些。它们仍然是直接调用的普通函数--但是它们被传递给一个隐藏指针,指向调用它们的对象的实例。在函数内部,您可以使用关键字this来引用该实例数据。因此,当您调用类似于a.func(b);的代码时,生成的代码与您为func(a, b);生成的代码非常相似
现在让我们考虑一下虚拟函数。这是我们进入vtable和vtable指针的地方。我们有足够多的间接,可能最好画一些图表,看看它是如何布局的。下面是最简单的例子:一个类的一个实例,其中包含两个虚拟函数:

因此,该对象包含其数据和指向vtable的指针。vtable包含指向该类定义的每个虚拟函数的指针。然而,我们为什么需要如此多的间接指导,这可能并不是马上就能显现出来的。为了理解这一点,让我们看下一个(稍微有点复杂)的情况:该类的两个实例:

请注意类的每个实例是如何拥有自己的数据的,但是它们都共享相同的vtable和相同的代码--如果我们有更多的实例,它们仍然会在同一个类的所有实例中共享一个vtable。
现在,让我们考虑派生/继承。例如,让我们将现有的类重命名为"Base",并添加一个派生类。因为我有想象力,所以我会把它命名为“衍生”。如上所述,基类定义了两个虚拟函数。派生类覆盖其中一个(但不是另一个):

当然,我们可以组合这两个类,具有每个基类和/或派生类的多个实例:

现在让我们更详细地研究一下这一点。关于派生的有趣之处在于,我们可以将对派生类对象的指针/引用传递给一个函数,该函数是为接收指针/引用基类而编写的,它仍然有效--但是如果调用一个虚拟函数,则得到实际类的版本,而不是基类。那这是怎么回事?我们如何将派生类的实例视为基类的实例,并使其仍然工作呢?为此,每个派生对象都有一个“基类子对象”。例如,让我们考虑如下代码:
struct simple_base {
int a;
};
struct simple_derived : public simple_base {
int b;
};在本例中,当您创建simple_derived的一个实例时,您将得到一个包含两个int的对象:a和b。a (基类部分)位于内存中对象的开头,b (派生类部分)紧随其后。因此,如果将对象的地址传递给期望基类实例的函数,则它使用基类中存在的部分(S),编译器在对象中放置的偏移量与它们在基类对象中的偏移量相同,因此函数可以在不知道它处理派生类的对象的情况下对它们进行操作。同样,如果您调用一个虚拟函数,它只需要知道vtable指针的位置。就其所关心的而言,类似于Base::func1的内容基本上只是表示它遵循vtable指针,然后在指定的偏移量(例如,第四个函数指针)处使用指向函数的指针。
至少现在,我将忽略多重继承。它给图片增加了相当大的复杂性(特别是当涉及到虚拟继承时),而且您根本没有提到它,所以我怀疑您是否真的关心它。
至于访问其中的任何一个,或者以任何方式使用,而不是简单地调用虚拟函数:您可能能够为特定的编译器想出一些东西--但是不要期望它是可移植的。尽管像调试器这样的东西经常需要查看这些东西,但是所涉及的代码往往是非常脆弱的,并且是特定于编译器的。
发布于 2017-08-25 18:44:04
虚拟表应该在类的实例之间共享。更确切地说,它存在于“类”级别,而不是实例级。每个实例都有一个指向虚拟表的指针的开销,如果在它的层次结构中有虚拟函数和类。
表本身至少是为每个虚拟函数保存指针所必需的大小。除此之外,它是一个实现细节,它实际上是如何定义的。有关此问题的更多详细信息,请查看这里。
发布于 2017-11-09 21:08:42
首先,下面的答案包含了您想知道的关于虚拟表的几乎所有信息:https://stackoverflow.com/a/16097013/8908931
如果您正在寻找更具体的内容(使用常规免责声明,这可能会在平台、编译器和CPU体系结构之间发生变化):
对于确切的大小,您可以在可执行文件中或内存中从可执行文件加载的段中找到此信息。可执行文件通常是ELF文件,这类文件包含运行程序所需的信息。这些信息的一部分是各种语言结构的符号,如变量、函数和虚拟表。对于每个符号,它包含内存中的大小。因此,按钮行,您将需要符号名称的虚拟表,和足够的知识在ELF,以提取您想要的。
https://stackoverflow.com/questions/45887575
复制相似问题