我想知道类对象(不是实例,但确切类)是如何存储在内存中的?
class A {
public:
int a;
virtual void f();
virtual ~A();
};
class B : public A {
public:
int b;
void f() final override;
};
我知道,在这种继承(B派生自A)的情况下,通常(标准没有强烈描述)我们有:
memory: ....AB...
其中 AB 是B的类对象(如果我理解正确的话)。 如果我们更深入(尝试 clang 和 gcc ),我们可以看到类似的东西(再次,标准中没有强烈描述):
A
vtptr*
int a
B
vtptr*
int b
好的,现在我们看到a
和b
属性存储在哪里。我们还看到指针到虚拟方法表。但是vtptr*
(虚拟方法表)实际存储在哪里?为什么不靠近课程?或者确实如此?
另外,这是另一个问题:我能够通过更改指针(简单逻辑)来更改虚拟方法表。我还可以安全地更改指向它的方法吗?
P.S。在你的问题中,你可以回答gcc和clang。 P.P.S.如果我在某处错了,请在答案中指出。
答案 0 :(得分:2)
C ++标准没有规定应该如何实现虚拟功能机制。实际上,所有C ++实现都使用每个类的虚函数表,并使用虚函数(称为多态类)在类的每个对象中使用虚函数表指针。然而,细节可能有所不同,特别是对于多继承和虚拟继承。
您可以阅读Stanley Lippman的经典着作Inside The C++ Object Model中的常见选择。
询问存储虚拟功能表的“位置”没有多大意义。它与任何静态变量非常相似:它的位置取决于实现,并且非常随意。关于
“为什么不靠近课程?
......这样的课程没有存储在任何地方,它们不是对象,所以这没有意义,抱歉。
对于给定的实现,您可以更有意义地询问存储在每个对象中的vtable指针在哪里?
通常是在对象的开头,但如果你从一个非多态的类派生,并添加一个虚函数,那么你可能会在其他地方得到vtable指针。或不。后一种可能性是static_cast
Derived*
到Base*
(或反之亦然)可以进行地址调整的原因之一,即与简单reinterpret_cast
不同。
答案 1 :(得分:1)
阅读virtual method table上的wikipage。
存储的vtable(本身)在哪里是特定于实现的(编译器,链接器,特定于操作系统)。但它经常存储在您的可执行文件的code segment中(就像文字字符串一样)。因此,通常(即没有多重继承)对象以指向其vtable的_vptr
指针开始。使用多个或虚拟继承,您可以拥有几个 vtable指针。
如评论所述,您不应该关心这些细节。如果您真的在意,请让编译器转储内部表示或发出汇编代码。 (例如使用g++ -fdump-tree-all -fverbose-asm -S
编译)
答案 2 :(得分:0)
但是vtptr *(虚拟方法表)实际上在哪里[指向]?为什么不靠近课程?或者确实如此?
它可能在任何地方......谁在乎?它的工作方式经常实现有点像这样......想象一下static
隐藏class A
成员:
VDT A::vdt = {
{ address of A::f code,
address of A::~A code },
miscellaneous type-specific information needed for dynamic cast etc.
};
确切的布局未知,但可能有virtual
个成员函数的地址数组。与任何static
信息一样,该地址与任何给定对象实例的地址无关......对象中指向虚拟调度表的指针允许这种解耦。
另外,这是另一个问题:我能够通过更改指针(简单逻辑)来更改虚拟方法表。我还可以安全地更改指向它的方法吗?
这是不安全的,即使它表面上有效也可能无法保持一致(例如,在编译器能够确定在编译时调用的特定覆盖的情况下,它可能绕过运行时虚拟发货表咨询完全)。