我正在“玩”C++ 中的虚拟继承,我想知道类对象是如何布局的。
我有这三个课程:
class A {
private:
int a;
public:
A() {this->a = 47;}
virtual void setInt(int x) {this->a = x;}
virtual int getInt() {return this->a;}
~A() {this->a = 0;}
};
class B {
private:
int b;
public:
B() {b = 48;}
virtual void setInt(int x) {this->b = x;}
virtual int getInt() {return this->b;}
~B() {b = 0;}
};
class C : public A, public B {
private:
int c;
public:
C() {c = 49;}
virtual void setInt(int x) {this->c = x;}
virtual int getInt() {return this->c;}
~C() {c = 0;}
};
(我认为他们是正确的:p)
I used -fdump-class-hierarchy
使用 g++,我得到了这个
Vtable for A
A::_ZTV1A: 4u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1A)
16 A::setInt
24 A::getInt
Class A
size=16 align=8
base size=12 base align=8
A (0x10209fb60) 0
vptr=((& A::_ZTV1A) + 16u)
Vtable for B
B::_ZTV1B: 4u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1B)
16 B::setInt
24 B::getInt
Class B
size=16 align=8
base size=12 base align=8
B (0x1020eb230) 0
vptr=((& B::_ZTV1B) + 16u)
Vtable for C
C::_ZTV1C: 8u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1C)
16 C::setInt
24 C::getInt
32 (int (*)(...))-0x00000000000000010
40 (int (*)(...))(& _ZTI1C)
48 C::_ZThn16_N1C6setIntEi
56 C::_ZThn16_N1C6getIntEv
Class C
size=32 align=8
base size=32 base align=8
C (0x1020f5080) 0
vptr=((& C::_ZTV1C) + 16u)
A (0x1020ebd90) 0
primary-for C (0x1020f5080)
B (0x1020ebe00) 16
vptr=((& C::_ZTV1C) + 48u)
现在那些到底是什么(int (*)(...))-0x00000000000000010
and C::_ZThn16_N1C6setIntEi and (int (*)(...))0
??
有人可以解释一下转储吗?
谢谢。
我不能 100% 确定这个答案是正确的,但这是我的最佳猜测。
当您有一个多重且非虚拟继承的类时,该类的布局通常是第一个基本类型的完整对象,然后是第二个基本类型的完整对象,然后是对象本身的数据。如果您查看 B,您可以看到 A 对象的 vtable 指针,如果您查看 C,您可以看到 A 和 B 对象都有指向 vtable 的指针。
因为对象是这样布局的,这意味着如果您有一个B*
指针指向一个C
对象,指针实际上不会位于对象的基址;相反,它会指向中间的某个地方。这意味着如果您需要将对象转换为A*
,你需要调整B*
指针一定量以将其跳回到对象的开头。为了做到这一点,编译器需要在某处编码您需要跳回以到达对象开头的字节数。我认为第一个(int(*)(...))
实际上只是您需要查看才能到达对象开头的原始字节数。如果你注意到的话,对于A
vtable this 指针为 0(因为 A 的 vtable 位于对象的开头,对于B
vtable(因为它也位于对象的开头。但是,请注意C
vtable 有两部分 - 第一部分是 vtableA
,它的第一个疯狂条目也为零(因为如果你在A
vtable,不需要做任何调整)。然而,在该表的前半部分之后似乎是B
vtable,并注意它的第一个条目是十六进制值-0x10
。如果你看一下C
对象布局,你会注意到B
vtable指针后16个字节A
虚函数表指针。这-0x10
值可能是您需要跳回的校正偏移量B
vtable 指针返回到对象的根。
每个 vtable 的第二个疯狂条目似乎是指向 vtable 本身的指针。请注意,它始终等于 vtable 对象的地址(比较 vtable 的名称和它所指向的内容)。如果您想要进行任何类型的运行时类型识别,这将是必要的,因为这通常涉及查看 vtable 的地址(或至少是靠近其前面的地址)。
最后,至于为什么在末尾有神秘命名的 setInt 和 getInt 函数C
vtable,我很确定那是因为C
type 继承了两组不同的函数,名为setInt
and getInt
- 一通A
和一通B
。如果我不得不猜测,这里的修改是为了确保编译器内部可以区分两个虚拟函数。
希望这可以帮助!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)