我正在“玩”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)
我使用了-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
和C::_ZThn16_N1C6setIntEi and (int (*)(...))0
到底是什么?
有人可以解释转储吗?
谢谢。
答案 0 :(得分:6)
我不是100%确定这个答案是正确的,但这是我最好的猜测。
当你有一个继承multiply和non-virtually的类时,类的布局通常是第一个基本类型的完整对象,然后是第二个基本类型的完整对象,然后是对象本身的数据。如果你看B,你可以看到A对象的vtable指针,如果你看C,你可以看到有关A和B对象的vtable的指针。
因为对象以这种方式布局,这意味着如果你有一个指向B*
对象的C
指针,指针实际上不在对象的底部;相反它会指向中间的某个地方。这意味着如果您需要将对象强制转换为A*
,则需要将B*
指针调整一些,以将其跳回到对象的开头。为了做到这一点,编译器需要在某处编码你需要跳回到达对象开头的字节数。我认为第一个(int(*)(...))
实际上只是你需要看到的原始字节数才能到达对象的开头。如果您注意到,对于A
vtable,此指针为0(因为A的vtable位于对象的开头,而B
vtable也是如此(因为它也存在但是,请注意C
vtable有两个部分 - 第一部分是A
的vtable,第一个疯狂的条目也是零(因为如果你是在A
vtable,您不需要进行任何调整。)但是,在此表的前半部分显示为B
vtable之后,请注意其第一个条目是十六进制值-0x10
。如果查看C
对象布局,您会注意到B
vtable指针在A
vtable指针之后是16个字节。这个{{1值可能是您需要跳过-0x10
vtable指针以返回对象根目录的纠正偏移量。
每个vtable的第二个疯狂条目似乎是vtable本身的指针。请注意,它始终等于vtable对象的地址(比较vtable的名称和它所指向的名称)。如果你想进行任何类型的运行时类型识别,这是必要的,因为这通常涉及查看vtable的地址(或至少在它前面的东西)。
最后,至于为什么在B
vtable的末尾有隐含命名的setInt和getInt函数,我很确定这是因为C
类型继承了两组不同的函数C
和setInt
- 一个到getInt
,一个到A
。如果我不得不猜测,这里的修改是为了确保编译器内部可以区分两个虚函数。
希望这有帮助!
答案 1 :(得分:6)
这是你的转储通过c ++ filt:
Vtable for A
A::vtable for A: 4u entries
0 (int (*)(...))0
8 (int (*)(...))(& typeinfo for A)
16 A::setInt
24 A::getInt
Class A
size=16 align=8
base size=12 base align=8
A (0x10209fb60) 0
vptr=((& A::vtable for A) + 16u)
Vtable for B
B::vtable for B: 4u entries
0 (int (*)(...))0
8 (int (*)(...))(& typeinfo for B)
16 B::setInt
24 B::getInt
Class B
size=16 align=8
base size=12 base align=8
B (0x1020eb230) 0
vptr=((& B::vtable for B) + 16u)
Vtable for C
C::vtable for C: 8u entries
0 (int (*)(...))0
8 (int (*)(...))(& typeinfo for C)
16 C::setInt
24 C::getInt
32 (int (*)(...))-0x00000000000000010
40 (int (*)(...))(& typeinfo for C)
48 C::non-virtual thunk to C::setInt(int)
56 C::non-virtual thunk to C::getInt()
Class C
size=32 align=8
base size=32 base align=8
C (0x1020f5080) 0
vptr=((& C::vtable for C) + 16u)
A (0x1020ebd90) 0
primary-for C (0x1020f5080)
B (0x1020ebe00) 16
vptr=((& C::vtable for C) + 48u)
不知道(int (*)(...))-0x00000000000000010
和(int (*)(...))0
是什么
C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int)
部分是“在存在多个或虚拟继承时虚拟函数调用的优化”,如here所述。