解密vtable转储

pvi*_*nis 9 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;}
};
Run Code Online (Sandbox Code Playgroud)

(我认为他们是正确的:p)

我用-fdump-class-hierarchyg ++,我得到了这个

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)
Run Code Online (Sandbox Code Playgroud)

现在那些是什么(int (*)(...))-0x00000000000000010C::_ZThn16_N1C6setIntEi and (int (*)(...))0?? 有人可以解释转储吗?

谢谢.

tem*_*def 6

我不是百分百肯定这个答案是正确的,但这是我最好的猜测.

当你有一个继承multiply和non-neural的类时,类的布局通常是第一个基本类型的完整对象,然后是第二个基本类型的完整对象,然后是对象本身的数据.如果你看B,你可以看到A对象的vtable指针,如果你看C,你可以看到有关A和B对象的vtable的指针.

因为对象以这种方式布局,这意味着如果你有一个B*指针指向一个C对象,指针实际上不会在对象的底部; 相反它会指向中间的某个地方.这意味着如果您需要将对象强制转换为a A*,则需要将B*指针调整一些以将其跳回到对象的开头.为了做到这一点,编译器需要在某处编码你需要跳回到达对象开头的字节数.我认为第一个(int(*)(...))实际上只是你需要查看的原始字节数才能到达对象的开头.如果您注意到,对于Avtable,此指针为0(因为A的vtable位于对象的开头,并且Bvtable 也是如此(因为它也位于对象的开头.但是,请注意该C虚函数表有两个部分-第一部分是虚函数表A,它的第一项疯狂零以及(因为如果你在A虚函数表,你不需要做任何调整)但是,第一个后.这个表的一半似乎是Bvtable,并注意到它的第一个条目是十六进制值-0x10.如果你看一下C对象布局,你会注意到Bvtable指针在vtable指针之后是16个字节A.这个-0x10值可能是您需要跳过Bvtable指针以返回对象根目录的校正偏移量.

每个vtable的第二个疯狂条目似乎是vtable本身的指针.请注意,它始终等于vtable对象的地址(比较vtable的名称和它所指向的名称).如果您想要进行任何类型的运行时类型识别,这将是必要的,因为这通常涉及查看vtable的地址(或至少在其前面的某些地方).

最后,为什么会出现在年底的隐晦的名为SETINT和getInt功能C虚函数表,我敢肯定那是因为C类型继承两套不同的命名函数setIntgetInt-一个通过A一根贯通B.如果我不得不猜测,这里的修改是为了确保编译器内部可以区分两个虚函数.

希望这可以帮助!

  • 在第一个数字“ -0x10”上,我也将其视为子对象在最终对象内的偏移量。关于为什么会出现……我在您的推理中并不完全同意,因为编译器在执行强制类型转换(无论是隐式还是显式)时都会看到所有类定义,因此这并不是对编译器的提示。然后我想到了其他可能的原因,我唯一想到的就是,当通过指向`B`的指针进行删除时,编译器可以获得指向开始释放内存的指针。但是我不确定。 (2认同)
  • 在第二个条目上,这些很可能不是指向vtable的指针,而是指向与该特定实例相关联的typeinfo对象的指针。注意具体的值:A :: _ ZTV1A vs._ZTI1A,并且A对象中的vptr设置为((&A :: _ ZTV1A-16u)...。 (2认同)

Eug*_*nca 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)
Run Code Online (Sandbox Code Playgroud)

不知道的(int (*)(...))-0x00000000000000010(int (*)(...))0有.
C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int)部分是一个"虚函数的优化在多个或虚拟继承的存在所谓的"所描述这里.