gpt4 book ai didi

c++ - 编译器关于此指针、虚函数和多重继承的详细信息

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:24:00 25 4
gpt4 key购买 nike

我正在阅读 Bjarne 的论文:Multiple Inheritance for C++ .

在第 3 节第 370 页中,Bjarne 说“编译器将成员函数的调用转换为带有“额外”参数的“普通”函数调用;该“额外”参数是指向成员函数所针对的对象的指针叫做。”

我对这个额外的论点感到困惑。请看以下两个例子:

示例 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); };

类 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页)

给定两个类
class A {...};
class B {...};
class C: A, B {...};

类 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 写道:“当然,B::bf() 期望 B*(成为它的 this 指针)。”编译器将调用转换为:
bf__F1B((B*)((char*)pc+delta(B)), 2);

为什么这里我们需要一个 B* 指针作为 this ?
如果我们只是传递一个 *C 指针作为 this ,我认为我们仍然可以正确访问 B 的成员。例如,要在 B::bf() 中获取类 B 的成员,我们只需要执行以下操作:*(this+offset)。这个偏移量可以被编译器知道。这是正确的吗?

跟进示例 1 和 2 的问题:

(1)当是线性链式推导时(例1),为什么可以预期C对象和B对象在同一个地址,依次是A子对象?使用C*指针访问示例1中函数B::g内的B类成员没有问题吗?比如我们要访问成员b,运行时会发生什么? *(电脑+8)?

(2)为什么我们可以对多重继承使用相同的内存布局(线性链式推导)?假设在示例 2 中,类 A , B , C具有与示例 1 完全相同的成员。 A : int af ; B : int bbf (或称之为 g); C : int ch .为什么不直接使用内存布局,例如:
 -----------               
+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 , pbpc有相同的地址。
class A {...};
class B {...};
class C: A, B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;

现在, pcpa具有相同的地址,而 pbpa 有一些偏移和 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 部分)。但是,在编译时通常不知道 Bpb 指出是 C 的一部分所以编译器不能减去常量 delta(B) .

为什么我们不知道 B pb指向的对象是 C 的一部分在编译时?据我了解, B* pb = new C , pb指向一个创建的 C对象和 C继承自 B , 所以一个 B指针 pb 指向 C 的一部分.

(3) 假设我们不知道 B指向 pb 的指针是 C 的一部分在编译时。所以我们必须存储实际与 vtbl 一起存储的运行时的 delta(B)。所以 vtbl 条目现在看起来像:
struct vtbl_entry {
void (*fct)();
int delta;
}

比亚内写道:
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

我在这里完全困惑。为什么 (B*) 在 (*vt->fct)((B*)((char*)pb+vt->delta)) 中不是 (C*) ???根据我的理解和 Bjarne 在 5.1 节第 377 页第一句的介绍,我们应该将 C* 作为 this 传递。这里!!!!!!

紧接着上面的代码片段,Bjarne 继续写:
请注意,对象指针可能需要调整为 po
在查找指向 vtbl 的成员之前,int 到正确的子对象。

天啊!!!我完全不知道 Bjarne 想说什么?你能帮我解释一下吗?

最佳答案

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/虚拟调度表,类型信息)。

相反, 来电有责任提取有效的 B*B涉及任何实际运行时类型的子对象(例如 C )。在这种情况下, C*保存全局的起始地址 C可能与 A 的地址匹配的对象子对象,以及 B子对象是一些固定但非 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”处;如果您拨打 B::bf使用 C*那么编译器生成的调用代码将需要添加 4 以形成隐式 this参数 B::bf() . B::bf()不能简单地告诉在哪里 AC从 +0 开始: B::bf()对这两个类一无所知,也不知道如何到达 b或者它的 RTTI,如果你给它一个指向它自己的 +4 地址以外的任何东西的指针。

关于c++ - 编译器关于此指针、虚函数和多重继承的详细信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30747633/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com