Зачем нам нужен «этот корректор указателя»? - PullRequest
8 голосов
/ 14 августа 2010

Я читал о регуляторе от здесь .Вот несколько цитат:

Теперь есть только один метод QueryInterface, но есть две записи, по одной для каждой таблицы.Помните, что каждая функция в vtable получает соответствующий указатель на интерфейс в качестве параметра this.Это прекрасно для QueryInterface (1);его указатель на интерфейс совпадает с указателем на интерфейс объекта.Но это плохая новость для QueryInterface (2), так как его интерфейсный указатель - q, а не p.

Вот тут-то и поступает корректор.1011 * каждая функция в vtable получает соответствующий указатель на интерфейс в качестве параметра "this""?Это единственный ключ (базовый адрес), используемый методом интерфейса для определения местоположения членов данных в экземпляре объекта?

Обновление

Вот мое последнее понимание:

На самом делеМой вопрос не о назначении параметра this , а о том, почему мы должны использовать соответствующий указатель интерфейса в качестве this параметр.Извините за мою неопределенность.

Помимо использования указателя интерфейса в качестве локатора / точки опоры в макете объекта.Конечно, есть и другие способы сделать это, если вы являетесь разработчиком компонента.

Но это не относится к клиентам нашего компонента.

Когда компонентпостроен в COM, клиенты нашего компонента ничего не знают о внутренностях нашего компонента. Клиенты могут удерживать только указатель интерфейса, и именно этот указатель будет передан в метод интерфейса как этот параметр .При этом ожидании компилятор не имеет другого выбора, кроме как сгенерировать код метода интерфейса на основе этого конкретного этого указателя.

Таким образом, приведенные выше рассуждения приводят к следующему результату:

необходимо убедиться, что каждая функция в vtable должна получить соответствующий указатель интерфейса в виде "Параметр this ".

В случае" блока управления указателем "для одного метода QueryInterface () существуют 2 разные записи, другими словами, 2 разных указателя интерфейсаможет использоваться для вызова метода QueryInterface (), но компилятор генерирует только 1 копию метода QueryInterface ().Таким образом, если один из интерфейсов выбран компилятором в качестве указателя this, нам нужно настроить другой для выбранного.Это то, для чего создан этот блок настроек.

BTW-1, что если компилятор может сгенерировать 2 разных экземпляра метода QueryInterface ()?Каждый основан на соответствующем указателе интерфейса.Для этого не понадобится корректор, но потребуется больше места для хранения дополнительного, но похожего кода.

BTW-2: кажется, что иногда вопрос не имеет разумного объяснения с точки зрения разработчика,но может быть лучше понято с точки зрения пользователя.

Ответы [ 5 ]

11 голосов
/ 17 августа 2010

Убирая часть COM из вопроса, блок настройки указателя this - это фрагмент кода, который гарантирует, что каждая функция получает указатель this, указывающий на подобъект конкретного типа. Проблема заключается в множественном наследовании, когда базовый и производный объекты не выровнены.

Рассмотрим следующий код:

struct base {
   int value;
   virtual void foo() { std::cout << value << std::endl; }
   virtual void bar() { std::cout << value << std::endl; }
};
struct offset {
   char space[10];
};
struct derived : offset, base {
   int dvalue;
   virtual void foo() { std::cout << value << "," << dvalue << std::endl; }
};

(И не обращать внимания на отсутствие инициализации). Субобъект base в derived не выровнен с началом объекта, так как между [1] находится offset. Когда указатель на derived приведен к указателю на base (включая неявные приведения, но не переинтерпретацию приведений, которые могут привести к UB и потенциальной смерти), значение указателя смещается так, что (void*)d != (void*)((base*)d) для предполагаемого объекта d типа derived.

Теперь рассмотрим использование:

derived d;
base * b = &d; // This generates an offset
b->bar();
b->foo();

Проблема возникает, когда функция вызывается из base указателя или ссылки. Если механизм виртуальной диспетчеризации обнаруживает, что окончательный переопределитель находится в base, то указатель this должен ссылаться на объект base, как в b->bar, где неявный указатель this является тем же адресом, хранящимся в b. Теперь, если последний переопределитель находится в производном классе, как и в случае b->foo(), указатель this должен быть выровнен с началом субобъекта типа, в котором найден последний переопределитель (в данном случае derived).

Компилятор создает промежуточный фрагмент кода. Когда вызывается механизм виртуальной диспетчеризации и перед отправкой на derived::foo промежуточный вызов получает указатель this и вычитает смещение к началу объекта derived. Эта операция аналогична понижению static_cast<derived*>(this). Помните, что на данный момент указатель this имеет тип base, поэтому он был изначально смещен, и это фактически возвращает исходное значение &d.

[1] Существует смещение даже в случае интерфейсов - в смысле Java / C #: классы, определяющие только виртуальные методы - по мере необходимости сохранить таблицу в vtable этого интерфейса.

3 голосов
/ 20 августа 2010

Вот статья о внутренностях MSVC от одного из дизайнеров. Это объясняет это и многие другие детали реализации MSVC. Вы также можете проверить мою статью на OpenRCE о том, как все это выглядит в сборке.

0 голосов
/ 18 августа 2010

Я думаю, что важно указать, что в C ++ нет такой сущности, как «указатель интерфейса» или что-то подобное. В лучшем случае это идиома, построенная на концепции ограниченного абстрактного класса, но все же остающаяся классом. Таким образом, все правила, применяемые к ученикам и обрабатывающие «это», по-прежнему применяются без изменений. Таким образом, принципиально интерфейсный класс должен вести себя как отдельные классы заданного типа независимо от их функции и возможной иерархии наследования.

Мы можем использовать механизм вызова виртуального метода, чтобы добраться до фактического (динамического типа) объекта, представленного базовым классом (интерфейса). Как это делается, зависит от конкретной реализации, включая такие понятия, как таблица виртуальных методов и «корректоры». Обычно компилятор может использовать свой начальный указатель «this», чтобы найти VMT, а затем фактическую реализацию данной функции и вызвать ее с возможной настройкой указателя «this». Корректировка thunk обычно необходима для выполнения последнего вызова, если схема памяти базового класса отличается от производного, на который мы ссылаемся, как в случае множественного наследования.

0 голосов
/ 14 августа 2010

Да, this необходим для определения места начала объекта. Вы пишете в своем коде:

variable = 10;

где variable - переменная-член. Прежде всего, к какому объекту он принадлежит? Он принадлежит объекту, на который указывает указатель this. Так что на самом деле

this->variable = 10;

теперь C ++ должен сгенерировать код, который будет фактически выполнять работу - копировать данные. Для этого ему нужно знать смещение между началом объекта и переменной-членом. Согласно соглашению this всегда указывает на начало объекта, поэтому смещение может быть постоянным:

*(reinterpret_cast<int*>( reinterpret_cast<char*>( this ) + variableOffset ) ) = 10; //assuming variable is of type int
0 голосов
/ 14 августа 2010

Является ли это единственной подсказкой (базовым адресом), используемой интерфейсным методом для определения местоположения членов данных в экземпляре объекта?

Да, это действительно все, что нужно.

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