编译器关于 this 指针、虚函数和多重继承的详细信息
compiler's detail of this pointer, virtual function and multiple-inheritance
我正在阅读 Bjarne 的论文:Multiple Inheritance for C++。
在第 3 节的第 370 页中,Bjarne 说 "The compiler turns a call of a member function into an "普通" 函数调用带有 "extra" 参数;"extra" 参数是指向对象的指针,该对象的成员函数被调用。"
我对额外的 this 参数感到困惑。请看下面两个例子:
示例 1:(第 372 页)
class A {
int a;
virtual void f(int);
virtual void g(int);
virtual void h(int);
};
class B : A {int b; void g(int); };
class C : B {int c; void h(int); };
A class c 对象 C 看起来像:
C:
----------- vtbl:
+0: vptr --------------> -----------
+4: a +0: A::f
+8: b +4: B::g
+12: c +8: C::h
----------- -----------
对虚函数的调用被编译器转换为间接调用。例如,
C* pc;
pc->g(2)
变成类似:
(*(pc->vptr[1]))(pc, 2)
Bjarne的论文告诉了我以上结论。路过的this
点是C*.
在下面的例子中,Bjarne 讲述了另一个让我完全困惑的故事!
示例 2:(第 373 页)
给定两个 classes
class A {...};
class B {...};
class C: A, B {...};
class C 的对象可以像这样布置为连续对象:
pc--> -----------
A part
B:bf's this--> -----------
B part
-----------
C part
-----------
给定 C* 调用 B 的成员函数:
C* pc;
pc->bf(2); //assume that bf is a member of B and that C has no member named bf.
Bjarne 写道:"Naturally, B::bf() expects a B* (to become its this pointer)." 编译器将调用转换为:
bf__F1B((B*)((char*)pc+delta(B)), 2);
为什么这里我们需要一个B*指针作为this
?
如果我们只传递一个 *C 指针作为 this
,我认为我们仍然可以正确访问 B 的成员。例如,要在 B::bf() 中获取 class B 的成员,我们只需要执行如下操作: *(this+offset)。编译器可以知道此偏移量。这样对吗?
跟进示例 1 和 2 的问题:
(1)当是线性链推导时(例1),为什么C对象可以期望和B在同一个地址,又是A的子对象?例1中函数B::g中使用C*指针访问classB的成员没有问题?比如我们要访问成员b,运行时会发生什么? *(pc+8)?
(2)为什么多重继承可以使用相同的内存布局(线性链推导)?假设在示例 2 中,class A
、B
、C
与示例 1 具有完全相同的成员。A
:int a
和 f
; B
:int b
和 bf
(或称之为 g
); C
:int c
和 h
。为什么不直接使用这样的内存布局:
-----------
+0: a
+4: b
+8: c
-----------
(3) 我写了一些简单的代码来测试线性链推导和多重继承之间的区别。
class A {...};
class B : A {...};
class C: B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;
cout << pc << pb << pa
说明pa
、pb
、pc
地址相同
class A {...};
class B {...};
class C: A, B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;
现在,pc
和 pa
具有相同的地址,而 pb
是 pa
和 pc
的一些偏移量。
为什么编译会产生这些差异?
示例 3:(第 377 页)
class A {virtual void f();};
class B {virtual void f(); virtual void g();};
class C: A, B {void f();};
A* pa = new C;
B* pb = new C;
C* pc = new C;
pa->f();
pb->f();
pc->f();
pc->g()
(1)第一个问题是关于pc->g()
的问题,涉及到例2的讨论,compile是否做了如下转换:
pc->g() ==> g__F1B((*B)((char*)pc+delta(B)))
或者我们必须等待运行时执行此操作?
(2) Bjarne 写道:在进入 C::f
时,this
指针必须指向 C
对象的开头(而不是 B
部分)。但是,在编译时通常不知道 pb
指向的 B
是 C
的一部分,因此编译器无法减去常量 delta(B)
.
为什么我们无法在编译时知道pb
指向的B
对象是C
的一部分?根据我的理解,B* pb = new C
,pb
指向一个创建的C
对象,而C
继承自B
,所以一个B
指针pb指向C
.
的一部分
(3) 假设我们不知道 pb
指向的 B
指针在编译时是 C
的一部分。所以我们必须存储实际与 vtbl 一起存储的运行时的 delta(B)。所以 vtbl 条目现在看起来像:
struct vtbl_entry {
void (*fct)();
int delta;
}
Bjarne 写道:
pb->f() // call of C::f:
register vtbl_entry* vt = &pb->vtbl[index(f)];
(*vt->fct)((B*)((char*)pb+vt->delta)) //vt->delta is a negative number I guess
我在这里完全糊涂了。为什么 (*vt->fct)((B*)((char*)pb+vt->delta))
中的 (B*) 不是 (C*)????根据我的理解和 Bjarne 在第 377 页 5.1 部分第一句的介绍,我们应该在这里传递一个 C* as this
!!!!!!
接着上面的代码片段,Bjarne 继续写道:
请注意,对象指针可能必须调整为 po
在查找指向 vtbl 的成员之前,将其转换为正确的子对象。
哦,伙计!我完全不知道 Bjarne 想说什么?你能帮我解释一下吗?
理论上应该是编译器会在代码中获取任何 this
,如果引用指针,那么它就知道 this
指的是什么。
Bjarne wrote: "Naturally, B::bf() expects a B* (to become its this pointer)." The compiler transforms the call into:
bf__F1B((B*)((char*)pc+delta(B)), 2);
Why here we need a B* pointer to be the this?
单独考虑 B
:编译器需要能够编译代码 ala B::bf(B* this)
。它不知道 类 可能会从 B
进一步派生什么(并且派生代码的引入可能要等到 B::bf
被编译很久之后才会发生)。 B::bf
的代码不会神奇地知道如何将指针从其他类型(例如 C*
)转换为 B*
它可以用来访问数据成员和运行时类型信息(RTTI / 虚拟调度 table, typeinfo).
相反,调用者有责任将有效的B*
提取到B
子涉及任何实际运行时类型的对象(例如 C
)。在这种情况下,C*
保存整个 C
对象的起始地址,该地址可能与 A
子对象的地址相匹配,而 B
子对象object 是一些固定的但非 0 的偏移量进一步进入内存:必须将偏移量(以字节为单位)添加到 C*
以获得有效的 B*
以调用 B::bf
- 当指针从 C*
类型转换为 B*
类型时完成调整。
(1) When it's a linear chain derivation (example 1), why the C object can be expected to be at the same address as the B and in turn A sub-objects? There is no problem to use a C*
pointer to access class B's members inside the function B::g
in example 1? For example, we want to access the member b, what will happen in runtime? *(pc+8)
?
线性推导 B : A 和 C : B 可以认为是在 A 的末尾依次添加 B 特定的字段,然后在 B 的末尾添加 C 特定的字段(仍然是 B 特定的字段)在 A) 的末尾。所以整个事情看起来像:
[[[A fields...]B-specific-fields....]C-specific-fields...]
^
|--- A, B & C all start at the same address
然后,当我们谈论 "B" 时,我们谈论的是所有嵌入的 A 字段以及添加的内容,而对于 "C",仍然有所有的 A 和 B 字段:他们都从同一个地址开始。
关于 *(pc+8)
- 是的(假设我们向地址添加 8 个字节,而不是添加指针大小的倍数的通常 C++ 行为)。
(2) Why can we use the same memory layout (linear chain derivation) for the multiple-inheritance? Assuming in example 2, class A, B, C have exactly the same members as the example 1. A: int a and f; B: int b and bf (or call it g); C: int c and h. Why not just use the memory layout like:
-----------
+0: a
+4: b
+8: c
-----------
没有理由 - 这正是发生的事情......相同的内存布局。区别在于 B 子对象不认为 A
是其自身的一部分。现在是这样的:
[[A fields...][B fields....]C-specific-fields...]
^ ^
\ A&C start \ B starts
因此,当您调用 B::bf
时,它想知道 B
对象的起始位置 - 您提供的 this
指针应该位于上面列表中的“+4”;如果您使用 C*
调用 B::bf
,那么编译器生成的调用代码将需要添加 4 以形成 B::bf()
的隐式 this
参数。 B::bf()
不能简单地被告知 A
或 C
从 +0 开始的位置:B::bf()
对这两个 类 中的任何一个一无所知,也不知道如何到达 b
或者它的 RTTI 如果你给它一个指向除它自己的 +4 地址之外的任何东西的指针。
如果您现在忽略函数调用,而是考虑将 C*
转换为 B*
,这在 before 之前可能更有意义] 呼叫 bf()
。由于 B
子对象与 C
对象的起始地址不同,因此需要调整地址。如果你只有一个 baseclass,也是一样的,但是偏移量 (delta(B)
) 是零,所以它被优化掉了。然后,仅更改附加到地址的类型。
顺便说一句:您引用的代码 (*((*pc)[1]))(pc, 2)
没有执行此转换,这在形式上是错误的。由于它无论如何都不是真正的代码,因此您必须通过阅读字里行间来推断。也许 Bjarne 只是打算在那里使用到 baseclass 的隐式转换。
顺便说一句 2:我认为您误解了带有虚函数的 classes 的布局。此外,正如免责声明,实际布局取决于系统,即编译器和 CPU。无论如何,考虑两个 classes A
和 B
具有单个虚函数:
class A {
virtual void fa();
int a;
};
class B {
virtual void fb();
int b;
};
布局将是:
----------- ---vtbl---
+0: vptr --------------> +0: A::fa
+4: a ----------
-----------
和
----------- ---vtbl---
+0: vptr --------------> +0: B::fb
+4: b ----------
-----------
换句话说,class A
的三保(B
等价):
- 给定一个指针
A*
,在该指针的零偏移处我找到 vtable 的地址。在 table 的位置零处,我找到了该对象的函数 fa()
的地址。虽然实际函数在派生的 classes 中可能会发生变化(由于覆盖),但 table 中的偏移量是固定的。
- vtable中函数的类型也是固定的。在 vtable 的零位置是一个函数,它接受一个隐藏的
A* this
作为参数。实际函数在派生中可能会被覆盖class,但这里函数的类型必须保留
- 给定一个指针
A*
,在该指针的偏移量四处,我找到成员变量 a
. 的值
现在,考虑第三个 class C
:
class C: A, B {
int c;
virtual void fa();
};
它的布局就像
----------- ---vtbl---
+0: vptr1 -------------> +0: A::fa
+4: a
+8: vptr2 -------------> +4: B::fb
+12: b +8: C::fc
+16: c ----------
-----------
是的,这个class包含两个vtable指针!原因很简单:classes A
和 B
的布局在编译时是固定的,参见上面的保证。为了允许用 C
替换 A
或 B
(Liskov 替换原则),这些布局保证 必须 保留,因为代码处理对象只知道例如A
,但不是 C
。
对此的一些评论:
- 上面,你已经找到了一个优化,class
C
的vtable指针已经和classA
的vtable指针合并了。这种简化仅适用于其中一个基classes,因此存在单继承和多继承之间的区别。
- 在
C
类型的对象上调用 fb()
时,编译器必须使用指针调用 B::fb
才能满足上述保证。为此,它必须调整对象的地址,使其在调用函数之前指向 B
(偏移量 +8)。
- 如果
C
覆盖 fb()
,编译器将生成该函数的两个版本。一个版本是针对 B
子对象的 vtable,然后将 B* this
作为隐藏参数。另一个将用于 C
class 的 vtable 中的单独条目,它需要一个 C*
。第一个只会调整从B
子对象到C
对象的指针(偏移量-8)并调用第二个
- 以上三项保证都不是必须的。您还可以将成员变量
a
和 b
的偏移量存储在 vtable 中。类似地,函数调用期间地址的调整可以通过 vtable 嵌入对象内部的信息间接完成。不过这样效率会低很多。
您示例中的函数 bf()
是 class B
的成员。在 B::bf()
内,您将能够访问 B
的所有成员。该访问是通过 this
指针执行的。因此,为了使该访问正常工作,您需要 B::bf()
内的 this
精确指向 B
。这就是为什么。
B::bf()
的实现不知道这个 B
对象是独立的 B
对象,还是嵌入到 C
对象中的 B
对象,或嵌入到其他东西中的其他 B
对象。因此,B::bf()
无法对 this
执行任何指针更正。 B::bf()
期望提前完成所有指针更正,因此当 B::bf()
开始执行时,this
精确指向 B
而不是其他任何地方。
这意味着当你调用pc->bf()
时,你必须将pc
的值调整一些固定的偏移量(B
在C
中的偏移量)和使用结果值作为 bf()
.
的 this
指针
我正在阅读 Bjarne 的论文:Multiple Inheritance for C++。
在第 3 节的第 370 页中,Bjarne 说 "The compiler turns a call of a member function into an "普通" 函数调用带有 "extra" 参数;"extra" 参数是指向对象的指针,该对象的成员函数被调用。"
我对额外的 this 参数感到困惑。请看下面两个例子:
示例 1:(第 372 页)
class A {
int a;
virtual void f(int);
virtual void g(int);
virtual void h(int);
};
class B : A {int b; void g(int); };
class C : B {int c; void h(int); };
A class c 对象 C 看起来像:
C:
----------- vtbl:
+0: vptr --------------> -----------
+4: a +0: A::f
+8: b +4: B::g
+12: c +8: C::h
----------- -----------
对虚函数的调用被编译器转换为间接调用。例如,
C* pc;
pc->g(2)
变成类似:
(*(pc->vptr[1]))(pc, 2)
Bjarne的论文告诉了我以上结论。路过的this
点是C*.
在下面的例子中,Bjarne 讲述了另一个让我完全困惑的故事!
示例 2:(第 373 页)
给定两个 classes
class A {...};
class B {...};
class C: A, B {...};
class C 的对象可以像这样布置为连续对象:
pc--> -----------
A part
B:bf's this--> -----------
B part
-----------
C part
-----------
给定 C* 调用 B 的成员函数:
C* pc;
pc->bf(2); //assume that bf is a member of B and that C has no member named bf.
Bjarne 写道:"Naturally, B::bf() expects a B* (to become its this pointer)." 编译器将调用转换为:
bf__F1B((B*)((char*)pc+delta(B)), 2);
为什么这里我们需要一个B*指针作为this
?
如果我们只传递一个 *C 指针作为 this
,我认为我们仍然可以正确访问 B 的成员。例如,要在 B::bf() 中获取 class B 的成员,我们只需要执行如下操作: *(this+offset)。编译器可以知道此偏移量。这样对吗?
跟进示例 1 和 2 的问题:
(1)当是线性链推导时(例1),为什么C对象可以期望和B在同一个地址,又是A的子对象?例1中函数B::g中使用C*指针访问classB的成员没有问题?比如我们要访问成员b,运行时会发生什么? *(pc+8)?
(2)为什么多重继承可以使用相同的内存布局(线性链推导)?假设在示例 2 中,class A
、B
、C
与示例 1 具有完全相同的成员。A
:int a
和 f
; B
:int b
和 bf
(或称之为 g
); C
:int c
和 h
。为什么不直接使用这样的内存布局:
-----------
+0: a
+4: b
+8: c
-----------
(3) 我写了一些简单的代码来测试线性链推导和多重继承之间的区别。
class A {...};
class B : A {...};
class C: B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;
cout << pc << pb << pa
说明pa
、pb
、pc
地址相同
class A {...};
class B {...};
class C: A, B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;
现在,pc
和 pa
具有相同的地址,而 pb
是 pa
和 pc
的一些偏移量。
为什么编译会产生这些差异?
示例 3:(第 377 页)
class A {virtual void f();};
class B {virtual void f(); virtual void g();};
class C: A, B {void f();};
A* pa = new C;
B* pb = new C;
C* pc = new C;
pa->f();
pb->f();
pc->f();
pc->g()
(1)第一个问题是关于pc->g()
的问题,涉及到例2的讨论,compile是否做了如下转换:
pc->g() ==> g__F1B((*B)((char*)pc+delta(B)))
或者我们必须等待运行时执行此操作?
(2) Bjarne 写道:在进入 C::f
时,this
指针必须指向 C
对象的开头(而不是 B
部分)。但是,在编译时通常不知道 pb
指向的 B
是 C
的一部分,因此编译器无法减去常量 delta(B)
.
为什么我们无法在编译时知道pb
指向的B
对象是C
的一部分?根据我的理解,B* pb = new C
,pb
指向一个创建的C
对象,而C
继承自B
,所以一个B
指针pb指向C
.
(3) 假设我们不知道 pb
指向的 B
指针在编译时是 C
的一部分。所以我们必须存储实际与 vtbl 一起存储的运行时的 delta(B)。所以 vtbl 条目现在看起来像:
struct vtbl_entry {
void (*fct)();
int delta;
}
Bjarne 写道:
pb->f() // call of C::f:
register vtbl_entry* vt = &pb->vtbl[index(f)];
(*vt->fct)((B*)((char*)pb+vt->delta)) //vt->delta is a negative number I guess
我在这里完全糊涂了。为什么 (*vt->fct)((B*)((char*)pb+vt->delta))
中的 (B*) 不是 (C*)????根据我的理解和 Bjarne 在第 377 页 5.1 部分第一句的介绍,我们应该在这里传递一个 C* as this
!!!!!!
接着上面的代码片段,Bjarne 继续写道: 请注意,对象指针可能必须调整为 po 在查找指向 vtbl 的成员之前,将其转换为正确的子对象。
哦,伙计!我完全不知道 Bjarne 想说什么?你能帮我解释一下吗?
理论上应该是编译器会在代码中获取任何 this
,如果引用指针,那么它就知道 this
指的是什么。
Bjarne wrote: "Naturally, B::bf() expects a B* (to become its this pointer)." The compiler transforms the call into:
bf__F1B((B*)((char*)pc+delta(B)), 2);
Why here we need a B* pointer to be the this?
单独考虑 B
:编译器需要能够编译代码 ala B::bf(B* this)
。它不知道 类 可能会从 B
进一步派生什么(并且派生代码的引入可能要等到 B::bf
被编译很久之后才会发生)。 B::bf
的代码不会神奇地知道如何将指针从其他类型(例如 C*
)转换为 B*
它可以用来访问数据成员和运行时类型信息(RTTI / 虚拟调度 table, typeinfo).
相反,调用者有责任将有效的B*
提取到B
子涉及任何实际运行时类型的对象(例如 C
)。在这种情况下,C*
保存整个 C
对象的起始地址,该地址可能与 A
子对象的地址相匹配,而 B
子对象object 是一些固定的但非 0 的偏移量进一步进入内存:必须将偏移量(以字节为单位)添加到 C*
以获得有效的 B*
以调用 B::bf
- 当指针从 C*
类型转换为 B*
类型时完成调整。
(1) When it's a linear chain derivation (example 1), why the C object can be expected to be at the same address as the B and in turn A sub-objects? There is no problem to use a
C*
pointer to access class B's members inside the functionB::g
in example 1? For example, we want to access the member b, what will happen in runtime?*(pc+8)
?
线性推导 B : A 和 C : B 可以认为是在 A 的末尾依次添加 B 特定的字段,然后在 B 的末尾添加 C 特定的字段(仍然是 B 特定的字段)在 A) 的末尾。所以整个事情看起来像:
[[[A fields...]B-specific-fields....]C-specific-fields...]
^
|--- A, B & C all start at the same address
然后,当我们谈论 "B" 时,我们谈论的是所有嵌入的 A 字段以及添加的内容,而对于 "C",仍然有所有的 A 和 B 字段:他们都从同一个地址开始。
关于 *(pc+8)
- 是的(假设我们向地址添加 8 个字节,而不是添加指针大小的倍数的通常 C++ 行为)。
(2) Why can we use the same memory layout (linear chain derivation) for the multiple-inheritance? Assuming in example 2, class A, B, C have exactly the same members as the example 1. A: int a and f; B: int b and bf (or call it g); C: int c and h. Why not just use the memory layout like:
-----------
+0: a
+4: b
+8: c
-----------
没有理由 - 这正是发生的事情......相同的内存布局。区别在于 B 子对象不认为 A
是其自身的一部分。现在是这样的:
[[A fields...][B fields....]C-specific-fields...]
^ ^
\ A&C start \ B starts
因此,当您调用 B::bf
时,它想知道 B
对象的起始位置 - 您提供的 this
指针应该位于上面列表中的“+4”;如果您使用 C*
调用 B::bf
,那么编译器生成的调用代码将需要添加 4 以形成 B::bf()
的隐式 this
参数。 B::bf()
不能简单地被告知 A
或 C
从 +0 开始的位置:B::bf()
对这两个 类 中的任何一个一无所知,也不知道如何到达 b
或者它的 RTTI 如果你给它一个指向除它自己的 +4 地址之外的任何东西的指针。
如果您现在忽略函数调用,而是考虑将 C*
转换为 B*
,这在 before 之前可能更有意义] 呼叫 bf()
。由于 B
子对象与 C
对象的起始地址不同,因此需要调整地址。如果你只有一个 baseclass,也是一样的,但是偏移量 (delta(B)
) 是零,所以它被优化掉了。然后,仅更改附加到地址的类型。
顺便说一句:您引用的代码 (*((*pc)[1]))(pc, 2)
没有执行此转换,这在形式上是错误的。由于它无论如何都不是真正的代码,因此您必须通过阅读字里行间来推断。也许 Bjarne 只是打算在那里使用到 baseclass 的隐式转换。
顺便说一句 2:我认为您误解了带有虚函数的 classes 的布局。此外,正如免责声明,实际布局取决于系统,即编译器和 CPU。无论如何,考虑两个 classes A
和 B
具有单个虚函数:
class A {
virtual void fa();
int a;
};
class B {
virtual void fb();
int b;
};
布局将是:
----------- ---vtbl---
+0: vptr --------------> +0: A::fa
+4: a ----------
-----------
和
----------- ---vtbl---
+0: vptr --------------> +0: B::fb
+4: b ----------
-----------
换句话说,class A
的三保(B
等价):
- 给定一个指针
A*
,在该指针的零偏移处我找到 vtable 的地址。在 table 的位置零处,我找到了该对象的函数fa()
的地址。虽然实际函数在派生的 classes 中可能会发生变化(由于覆盖),但 table 中的偏移量是固定的。 - vtable中函数的类型也是固定的。在 vtable 的零位置是一个函数,它接受一个隐藏的
A* this
作为参数。实际函数在派生中可能会被覆盖class,但这里函数的类型必须保留 - 给定一个指针
A*
,在该指针的偏移量四处,我找到成员变量a
. 的值
现在,考虑第三个 class C
:
class C: A, B {
int c;
virtual void fa();
};
它的布局就像
----------- ---vtbl---
+0: vptr1 -------------> +0: A::fa
+4: a
+8: vptr2 -------------> +4: B::fb
+12: b +8: C::fc
+16: c ----------
-----------
是的,这个class包含两个vtable指针!原因很简单:classes A
和 B
的布局在编译时是固定的,参见上面的保证。为了允许用 C
替换 A
或 B
(Liskov 替换原则),这些布局保证 必须 保留,因为代码处理对象只知道例如A
,但不是 C
。
对此的一些评论:
- 上面,你已经找到了一个优化,class
C
的vtable指针已经和classA
的vtable指针合并了。这种简化仅适用于其中一个基classes,因此存在单继承和多继承之间的区别。 - 在
C
类型的对象上调用fb()
时,编译器必须使用指针调用B::fb
才能满足上述保证。为此,它必须调整对象的地址,使其在调用函数之前指向B
(偏移量 +8)。 - 如果
C
覆盖fb()
,编译器将生成该函数的两个版本。一个版本是针对B
子对象的 vtable,然后将B* this
作为隐藏参数。另一个将用于C
class 的 vtable 中的单独条目,它需要一个C*
。第一个只会调整从B
子对象到C
对象的指针(偏移量-8)并调用第二个 - 以上三项保证都不是必须的。您还可以将成员变量
a
和b
的偏移量存储在 vtable 中。类似地,函数调用期间地址的调整可以通过 vtable 嵌入对象内部的信息间接完成。不过这样效率会低很多。
您示例中的函数 bf()
是 class B
的成员。在 B::bf()
内,您将能够访问 B
的所有成员。该访问是通过 this
指针执行的。因此,为了使该访问正常工作,您需要 B::bf()
内的 this
精确指向 B
。这就是为什么。
B::bf()
的实现不知道这个 B
对象是独立的 B
对象,还是嵌入到 C
对象中的 B
对象,或嵌入到其他东西中的其他 B
对象。因此,B::bf()
无法对 this
执行任何指针更正。 B::bf()
期望提前完成所有指针更正,因此当 B::bf()
开始执行时,this
精确指向 B
而不是其他任何地方。
这意味着当你调用pc->bf()
时,你必须将pc
的值调整一些固定的偏移量(B
在C
中的偏移量)和使用结果值作为 bf()
.
this
指针