解密vtable转储

时间:2011-01-05 21:59:44

标签: c++ inheritance dump class-hierarchy

我正在“玩”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 (*)(...))-0x00000000000000010C::_ZThn16_N1C6setIntEi and (int (*)(...))0到底是什么? 有人可以解释转储吗?

谢谢。

2 个答案:

答案 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类型继承了两组不同的函数CsetInt - 一个到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所述。