关于C ++对象布局中“this pointer adjustor”的问题

时间:2010-06-27 18:08:06

标签: c++ visual-c++

我对一个问题感到困惑:MS VC ++编译器在什么情况下生成this adjustor?请注意,此调整程序不一定在thunk中。以下是我的测试代码。

class myIUnknown
{
public:
    virtual void IUnknown_method1(void)=0;
    virtual void IUnknown_method2(void)=0;
    int data_unknown_1;
    int data_unknown_2;
};


class BaseX:public myIUnknown
{
public:
    BaseX(int);
    virtual void base_x_method1(void)=0;
    virtual void base_x_method2(void)=0;
    int data_base_x;
    int data_unknown_1;
    int data_unknown_2;
};

class BaseY:public myIUnknown
{
public:
    BaseY(int);
    virtual void base_y_method1(void);
    virtual void base_y_method2(void)=0;
    int data_base_y;
    int data_unknown_1;
    int data_unknown_2;
};

class ClassA:public BaseX, public BaseY
{
public:
    ClassA(void);
    //myIUnknown
    void IUnknown_method1(void);
    void IUnknown_method2(void);
    //baseX
    void base_x_method1(void) ;
    void base_x_method2(void) ;
    //baseY
    //void base_y_method1(void) ;
    void base_y_method2(void) ;
    virtual void class_a_method(void);
    int data_class_a;
    int data_unknown_1;
    int data_unknown_2;
};

对象布局如下:

1>  class ClassA    size(60):
1>      +---
1>      | +--- (base class BaseX)
1>      | | +--- (base class myIUnknown)
1>   0  | | | {vfptr}
1>   4  | | | data_unknown_1
1>   8  | | | data_unknown_2
1>      | | +---
1>  12  | | data_base_x
1>  16  | | data_unknown_1
1>  20  | | data_unknown_2
1>      | +---
1>      | +--- (base class BaseY)
1>      | | +--- (base class myIUnknown)
1>  24  | | | {vfptr}
1>  28  | | | data_unknown_1
1>  32  | | | data_unknown_2
1>      | | +---
1>  36  | | data_base_y
1>  40  | | data_unknown_1
1>  44  | | data_unknown_2
1>      | +---
1>  48  | data_class_a
1>  52  | data_unknown_1
1>  56  | data_unknown_2
1>      +---
1>  
1>  ClassA::$vftable@BaseX@:
1>      | &ClassA_meta
1>      |  0
1>   0  | &ClassA::IUnknown_method1
1>   1  | &ClassA::IUnknown_method2
1>   2  | &ClassA::base_x_method1
1>   3  | &ClassA::base_x_method2
1>   4  | &ClassA::class_a_method
1>  
1>  ClassA::$vftable@BaseY@:
1>      | -24
1>   0  | &thunk: this-=24; goto ClassA::IUnknown_method1 <=====in-thunk "this adjustor"
1>   1  | &thunk: this-=24; goto ClassA::IUnknown_method2 <=====in-thunk "this adjustor"
1>   2  | &BaseY::base_y_method1
1>   3  | &ClassA::base_y_method2
1>  
1>  ClassA::IUnknown_method1 this adjustor: 0
1>  ClassA::IUnknown_method2 this adjustor: 0
1>  ClassA::base_x_method1 this adjustor: 0
1>  ClassA::base_x_method2 this adjustor: 0
1>  ClassA::base_y_method2 this adjustor: 24  <============non-in-thunk "this adjustor"
1>  ClassA::class_a_method this adjustor: 0

我发现在下面的调用中,生成了指针调整器

in-thunk 此调节器

pY->IUnknown_method1();//adjustor this! this-=24  pY-24==>pA
pY->IUnknown_method2();//adjustor this! this-=24  pY-24==>pA

非thunk 此调节器

pA->base_y_method2();//adjustor this!   this+=24 pA+24==>pY
  • 有人能告诉我为什么编译器会在上面的调用中产生这个调整器吗?

  • 编译器会在什么情况下生成此调整器

非常感谢。

4 个答案:

答案 0 :(得分:0)

我已经做了十多年的C ++,从来不需要担心这些。然而,看起来你“这个调节器”在MI期间对于不在结构开始的类而起作用。

答案 1 :(得分:0)

这是一个虚拟 - 虚拟步骤。

将该表视为虚拟 vtable(而不仅仅是vtable)。虚拟 - 虚拟步骤需要一些计算:给定 this 指针,计算vtable。 (或者,在这种情况下,给定一个vtable,计算另一个 vtable。)该计算由thunk执行。但是如果您不需要执行虚拟操作,那么您不需要找到其他vtable,也不需要执行计算,因此您不需要thunk。这就是为什么有些步骤只是偏移而其他步骤实现为thunk。这是虚拟的虚拟步骤。

答案 2 :(得分:0)

或许最简单的方法是考虑如何(通常)在C ++中实现单继承。考虑包含至少一个虚函数的层次结构:

struct Base { 
    int x;
    virtual void f() {}
    virtual ~Base() {}
};

struct Derived : Base { 
    int y;
    virtual void f() {}
    virtual ~Derived() {}
};

在典型的情况下,这将通过为每个类创建一个vtable来实现,并使用(隐藏的)vtable指针创建每个对象。每个对象(Base或Derived类)的vtable指针将使vtable指针位于结构中的相同偏移处,并且每个指针都包含指向虚拟函数(f和dtor)的指针虚拟表中的偏移量。

现在,考虑使用这些类型的多态,例如:

void g(Base&b) { 
    b.f();
}

由于Base和Derived(以及Base的任何其他派生词)都以相同的方式排列vtable,并且指向结构中相同偏移量的vtable的指针,编译器可以为此生成完全相同的代码,无论它是处理Base,Derived还是从Base派生的其他东西。

但是,当您向混合中添加多个继承时,这会发生变化。特别是,不能排列所有对象,因此指向vtable的指针始终位于每个对象的相同偏移处,原因很简单,即从两个基类派生的对象(可能) )指向两个单独的vtable,它们显然不能在结构中处于相同的偏移量(即,你不能将两个不同的东西放在完全相同的位置)。为了适应这种情况,您必须进行某种明确的调整。每个乘法派生类必须有一些方法让编译器找到所有基类的vtable。考虑这样的事情:

struct Base1 { 
    virtual void f() { }
};

struct Base2 { 
    virtual void g() {}
};

class Derived1 : Base1, Base2 { 
    virtual void f() {}
    virtual void g() {}
};

class Derived2 : Base2, Base1 {
    virtual void f() {}
    virtual void g() {}
};

在典型情况下,编译器将按照您指定基类的相同顺序排列vtable指针,因此Derived1将有一个指向Base1的vtable的指针,后跟指向Base2的vtable的指针。 Derived2将颠倒订单。

现在,假设执行对f()的多态调用的相同函数,但是将传递对Base1,Derived1或Derived2的引用。其中一个将几乎不可避免地指向Base1的vtable,其偏移量与其他偏移量不同。这是“this-adjustor”(或者你喜欢称之为它的任何东西)的来源 - 它为你尝试使用的基类找到了正确的偏移量,所以当你访问该类的成员时,你会得到正确的数据。

请注意,虽然我在这里使用了指向vtable的指针作为主要示例,但不是唯一的可能性。实际上,即使您在任何类中都没有虚函数,您仍然需要访问每个基类的数据,这需要进行相同的调整。

答案 3 :(得分:0)

您可能还希望查看我的关于MS C ++对象映射的文章,“C ++:Under the Hood”,仍然可用here

快乐的黑客攻击!