为什么我们需要“这个指针调整器重击"?
我从 这里.这里引用一段话:
I read about adjustor thunk from here. Here's some quotation:
现在,只有一个 QueryInterface方法,但有两个条目,一个对于每个 vtable.请记住,每个vtable 中的函数接收对应的接口指针作为它的这个"参数.这很好查询接口(1);它的界面指针与对象的相同接口指针.但这是个坏消息对于 QueryInterface (2),因为它接口指针是q,不是p.
Now, there is only one 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.
我想知道为什么vtable 中的每个函数都接收相应的接口指针作为其this"参数"?它是接口方法用来在对象实例中定位数据成员的唯一线索(基地址)吗?
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?
这是我的最新理解:
其实我的问题不是这个参数的用途,而是为什么我们要使用对应的接口指针作为这个参数.抱歉我的含糊不清.
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.
当组件以 COM 方式构建时,我们组件的客户端对我们组件的内部一无所知.客户端只能持有接口指针,而这正是将作为this参数传递给接口方法的指针.在这种期望下,编译器别无选择,只能根据这个特定的this指针生成接口方法的代码.
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:
必须确保每个功能在 vtable 中必须收到对应的接口指针作为它的这个"参数.
it must be assured that each function in a vtable must recieve the corresponding interface pointer as its "this" parameter.
在this pointeradjustor thunk"的情况下,单个 QueryInterface() 方法存在 2 个不同的条目,换句话说,可以使用 2 个不同的接口指针来调用 QueryInterface() 方法,但编译器只生成1 个 QueryInterface() 方法的副本.因此,如果编译器选择其中一个接口作为 this 指针,我们需要将另一个接口调整为所选择的接口.这就是这个调节器重击的目的.
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,如果编译器可以生成 2 个不同的 QueryInterface() 方法实例怎么办?每一个都基于相应的接口指针.这不需要调整器 thunk,但需要更多空间来存储额外但类似的代码.
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:从实施者的角度来看,有时一个问题似乎缺乏合理的解释,但从用户的角度可以更好地理解.
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.
推荐答案
去掉问题中的 COM 部分,this
指针调整器 thunk 是一段确保每个函数都得到this
指针,指向具体类型的子对象.这个问题来自多重继承,其中基对象和派生对象没有对齐.
Taking away the COM part from the question, the this
pointer adjustor thunk is a piece of code that makes sure that each function gets a this
pointer pointing to the subobject of the concrete type. The issue comes up with multiple inheritance, where the base and derived objects are not aligned.
考虑以下代码:
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; }
};
(并且忽略缺少初始化).derived
中的 base
子对象未与对象的开头对齐,因为在 [1] 之间有一个 offset
.当指向 derived
的指针转换为指向 base
的指针(包括隐式转换,但不重新解释会导致 UB 和潜在死亡的转换)时,指针的值会偏移因此 (void*)d != (void*)((base*)d)
用于类型为 derived
的假定对象 d
.
(And disregard the lack of initialization). The base
sub object in derived
is not aligned with the start of the object, as there is a offset
in between[1]. When a pointer to derived
is casted to a pointer to base
(including implicit casts, but not reinterpret casts that would cause UB and potential death) the value of the pointer is offsetted so that (void*)d != (void*)((base*)d)
for an assumed object d
of type derived
.
现在考虑一下用法:
derived d;
base * b = &d; // This generates an offset
b->bar();
b->foo();
从 base
指针或引用调用函数时会出现问题.如果虚拟调度机制发现最终的 overrider 在 base
中,那么指针 this
必须引用 base
对象,如 b->bar
,其中隐式 this
指针与存储在 b
中的地址相同.现在,如果最终覆盖器在派生类中,如 b->foo()
一样,this
指针必须与找到最终覆盖器的类型(在本例中为 derived
).
The issue comes when a function is called from a base
pointer or reference. If the virtual dispatch mechanism finds that the final overrider is in base
, then the pointer this
must refer to the base
object, as in b->bar
, where the implicit this
pointer is the same address stored in b
. Now if the final overrider is in a derived class, as with b->foo()
the this
pointer has to be aligned with the beginning of the sub object of the type where the final overrider is found (in this case derived
).
编译器所做的是创建一段中间代码.当调用虚拟分派机制时,在分派到 derived::foo
之前,中间调用获取 this
指针并将偏移量减去 derived 的开头
对象.此操作与向下转换 static_cast
相同.请记住,此时,this
指针的类型为 base
,因此它最初是偏移的,并且 this 有效地返回了原始值 &d
.
What the compiler does is creating an intermediate piece of code. When the virtual dispatch mechanism is called, and before dispatching to derived::foo
the intermediate call takes the this
pointer and substracts the offset to the beginning of the derived
object. This operation is the same as a downcast static_cast<derived*>(this)
. Remember that at this point, the this
pointer is of type base
, so it was initially offsetted, and this effectively returns the original value &d
.
[1]即使在 interfaces 的情况下也存在偏移――在 Java/C# 意义上:类仅定义虚拟方法――如他们需要将表存储到该接口的 vtable 中.
[1]There is an offset even in the case of interfaces --in the Java/C# sense: classes defining only virtual methods-- as they need to store a table to that interface's vtable.
相关文章