2011-01-05 42 views
7

我在C++中“玩”虚拟继承,我想知道如何布置类对象。 我有那些三类:破译vtable转储

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 ?? 有人可以解释转储吗?

谢谢。

+1

它的未定义。每个编译器(甚至编译器的版本)都会以不同的方式进行操作。 – 2011-01-05 22:15:25

+0

你可以使用C++ filt来解码'_ZTI1C'其他的位置可能会在编译器的后续阶段被函数指针填充。 – 2011-01-05 22:20:25

回答

6

我不是100%确定这个答案是正确的,但这是我的最佳猜测。

当你有一个继承多重且非虚拟的类时,类的布局通常是第一个基类型的完整对象,然后是第二个基类型的完整对象,然后是对象本身的数据。如果你看看B,你可以看到A对象的vtable指针,并且如果你看看C,你可以看到有指向A和B对象的vtable的指针。因为这些对象是这样布局的,这意味着如果你有一个B*指针指向一个C对象,指针实际上不在对象的底部;相反它会指向中间的某个地方。这意味着如果您需要将对象投射到A*,则需要调整B*指针的某个数值以将其跳回到对象的起始位置。为了做到这一点,编译器需要对需要跳回到对象开始处的字节数进行编码。我认为第一个(int(*)(...))实际上只是您需要查看的对象初始字节的原始数量。如果你会注意到,对于A这个vtable,这个指针是0(因为A的vtable在对象的起始处,而B vtable也是如此(因为它也位于对象的起始处)。请注意,C vtable有两个部分 - 第一部分是A的虚拟表,它的第一个疯狂条目也是零(因为如果你在A虚拟表,你不需要做任何调整)。但是,该表的前半部分看起来是B vtable,并且注意到它的第一个条目是十六进制值-0x10。如果查看C对象布局,您会注意到B vtable指针是16个字节A vtable指针之后。这个-0x10值可能是您需要跳过012的校正偏移量用于返回到对象根目录的vtable指针。

每个vtable的第二个疯狂条目似乎是指向vtable本身的指针。请注意,它始终等于vtable对象的地址(比较vtable的名称和指向的地址)。如果你想做任何类型的运行时类型标识,这将是必要的,因为通常需要查看vtable的地址(或者至少靠近它的前端)。

最后,为什么还有的隐晦命名SETINT和getInt功能在C虚函数表的末尾,我敢肯定那是因为C类型继承两套不同的命名setIntgetInt功能 - 通过A一个和一个到B。如果我不得不猜测,这里的重点是确保编译器内部可以区分这两个虚函数。

希望这会有所帮助!

+1

在第一个数字“-0x10”上,我也认为这是最终对象内子对象的偏移量。关于为什么会出现在这里......我不太同意你的推理,因为编译器在执行转换时(无论是隐式还是显式)都可以看到所有的类定义,所以它不是编译器的提示。然后我想到了其他可能的原因,我唯一能想到的是当通过指向'B'的指针删除时,编译器可以得到一个指向开始释放内存的指针。但我不确定这一点。 – 2011-01-05 22:47:23

+1

在第二个条目中,那些很可能不是指向vtable的指针,而是指向与特定实例关联的typeinfo对象的指针。请注意具体值:'A :: _ ZTV1A' vs'_ZTI1A',并且'A'对象中的vptr被设置为'(&A :: _ZTV1A - 16u)'... ...它们不重合。 – 2011-01-05 22:51:08

+0

这些都是非常好的观点。我很确定你在这两方面都是正确的。 – templatetypedef 2011-01-05 22:58:03

5

这是你的转储到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

+0

+1打我吧:)这是我明确表示的问题的唯一部分 - 如果您同时获得类转储和程序集,那么它很简单:将'this'指针偏移16并跳转到'setInt/getInt'(在每种情况下) – 2011-01-05 23:01:45

+0

@David:如果任何其他部分变得清晰,请将它们添加到答案中。 – 2011-01-06 02:31:49