Зачем нам нужен «этот корректор указателя»?

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

Теперь есть только один QueryInterface method, but there are two entries, one for each vtable. Remember that each function in a vtable receives the corresponding interface pointer as its "this" parameter. That's just fine for QueryInterface (1); its interface pointer is the same as the object's interface pointer. But that's bad news for QueryInterface (2), since its interface pointer is q, not p.

This is where the adjustor thunks come in.

I am wondering why "each function in a vtable receives the corresponding interface pointer as its "this" parameter"? Is it the only clue(base address) used by the interface method to locate data members within the object instance?

Update

Here is my latest understanding:

In fact, my question is not about the purpose of this parameter, but about why we have to use the corresponding interface pointer as the this parameter. Sorry for my vagueness.

Besides using the interface pointer as a locator/foothold within an object's layout. There're of course other means to do that, as long as you are the implementer of the component.

But this is not the case for the clients of our component.

When the component is built in COM way, clients of our component know nothing about the internals of our component. Clients can only take hold of the interface pointer, and this is the very pointer that will be passed into the interface method as the this parameter. Under this expectation, the compiler has no choice but to generate the interface method's code based on this specific this pointer.

So the above reasoning leads to the result that:

it must be assured that each function в vtable должен получить соответствующий указатель интерфейса в качестве его "this" parameter.

In the case of "this pointer adjustor thunk", 2 different entries exist for a single QueryInterface() method, in other words, 2 different interface pointers could be used to invoke the QueryInterface() method, but the compiler only generate 1 copy of QueryInterface() method. So if one of the interfaces is chosen by the compiler as the this pointer, we need to adjust the other to the chosen one. This is what the this adjustor thunk is born for.

BTW-1, what if the compiler can generate 2 different instances of QueryInterface() method? Each one based on the corresponding interface pointer. This won't need the adjustor thunk, but it would take more space to store the extra but similar code.

BTW-2: it seems that sometimes a question lacks a reasonable explanation from the implementer's point of view, but could be better understood from the user's pointer of view.

8
задан smwikipedia 15 August 2010 в 02:52
поделиться

5 ответов

Если убрать часть 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 в , производном не выровнен с началом объекта, так как есть смещение между [1]. Когда указатель на производный приводится к указателю на base (включая неявное приведение, но не переинтерпретирует приведение, которое могло бы вызвать UB и потенциальную смерть), значение указателя смещается так, чтобы (void *) d! = (Void *) ((base *) d) для предполагаемого объекта d типа , производного .

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

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

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

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

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

11
ответ дан 5 December 2019 в 12:06
поделиться

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

variable = 10;

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

this->variable = 10;

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

*(reinterpret_cast<int*>( reinterpret_cast<char*>( this ) + variableOffset ) ) = 10; //assuming variable is of type int
0
ответ дан 5 December 2019 в 12:06
поделиться

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

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

0
ответ дан 5 December 2019 в 12:06
поделиться

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

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

0
ответ дан 5 December 2019 в 12:06
поделиться

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

3
ответ дан 5 December 2019 в 12:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: