Вопросы о «регуляторе указателя» в макете объекта C ++ - PullRequest
1 голос
/ 27 июня 2010

Меня немного смущает один вопрос: при каких обстоятельствах компилятор MS VC ++ генерирует этот регулятор ? Обратите внимание, что этот регулятор не обязательно находится в 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

non-in-thunk этот регулятор :

pA->base_y_method2();//adjustor this!   this+=24 pA+24==>pY
  • Может кто-нибудь сказать мне, почему компилятор производит этот регулятор в приведенных выше вызовах?

  • При каких обстоятельствах компилятор сгенерирует этот регулятор ?

Большое спасибо.

Ответы [ 4 ]

0 голосов
/ 16 ноября 2010

Вы также можете просмотреть мою статью о сопоставлении объектов MS C ++, "C ++: Под капотом", которая по-прежнему доступна здесь .

Счастливого взлома!

0 голосов
/ 27 июня 2010

Это виртуально-виртуальный шаг.

Думайте о таблице как о виртуальной vtable (в отличие от просто vtable).Виртуальный виртуальный шаг требует некоторых вычислений: при наличии указателя this вычислите vtable.(Или, в данном случае, учитывая vtable, вычислите другой vtable.) Это вычисление выполняется thunk.Но если вам не нужно выполнять виртуальную операцию, вам не нужно находить другую виртуальную таблицу, и вам не нужно выполнять вычисления, поэтому вам не нужен блок.Вот почему некоторые шаги являются просто смещениями, а другие реализованы в виде блоков.Это тот виртуально-виртуальный шаг.

0 голосов
/ 27 июня 2010

Возможно, проще всего начать с размышлений о том, как одиночное наследование (обычно) реализовано в C ++. Рассмотрим иерархию, которая включает хотя бы одну виртуальную функцию:

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

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

В типичном случае это будет реализовано путем создания таблицы vtable для каждого класса и создания каждого объекта с (скрытым) указателем таблицы vtable. Указатель vtable для каждого объекта (класса Base или Derived) будет иметь указатель vtable с одинаковым смещением в структуре, и каждый будет содержать указатели на виртуальную функцию (f и dtor) с одинаковыми смещениями в виртуальный стол.

Теперь рассмотрим полиморфное использование этих типов, таких как:

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

Поскольку и Base, и Derived (и любые другие производные Base) имеют виртуальную таблицу, расположенную одинаково, и указатель на виртуальную таблицу с одинаковым смещением в структуре, компилятор может сгенерировать точно такой же код для этого независимо от того, имеет ли он дело с базой, производным или чем-то еще, полученным из базы.

Однако, когда вы добавляете множественное наследование в микс, это меняется. В частности, вы не можете упорядочить все свои объекты, чтобы указатель на виртуальную таблицу всегда имел одинаковое смещение в каждом объекте, по той простой причине, что объект, производный от двух базовых классов, (потенциально) иметь указатели на две отдельные таблицы, которые явно не могут быть с одинаковым смещением в структуре (т. е. вы не можете поместить две разные вещи в одно и то же место). Чтобы приспособиться к этому, вы должны сделать какую-то явную корректировку. Каждый умноженный производный класс должен иметь какой-то способ для компилятора найти виртуальные таблицы для всех базовых классов. Рассмотрим что-то вроде этого:

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 будет иметь указатель на vtable Base1, за которым следует указатель на vtable Base2. Derived2 обратит порядок.

Теперь, предполагая ту же функцию, которая выполняет полиморфный вызов f(), но будет передана ссылка на Base1, или Derived1, или Derived2. Один из них почти неизбежно будет иметь указатель на vtable Base1 с другим смещением, чем другие. Вот тут-то и появляется «this-Adjustor» (или как вы предпочитаете его называть) - он находит правильное смещение для базового класса, который вы пытаетесь использовать, поэтому, когда вы получаете доступ к членам этого класса, вы получаете правильные данные.

Обратите внимание, что хотя я использовал указатель на vtable в качестве основного примера, это не единственная возможность. На самом деле, даже если у вас нет виртуальных функций ни в одном из классов, вам все равно нужен доступ к данным для каждого базового класса, что требует такой же настройки.

0 голосов
/ 27 июня 2010

Я занимаюсь C ++ уже более десяти лет, и мне никогда не приходилось беспокоиться об этом.Однако, похоже, что вы «этот корректор» вступаете в игру во время МИ для классов, которые не находятся в начале структуры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...