C++多重继承内存寻址问题

C++ Multiple Inheritance Memory Addressing issue

本文关键字:问题 寻址 内存 多重继承 C++      更新时间:2023-10-16

请忽略 #include 部分,假设它们正确完成。这也可能是特定于实现的(但 vtables 的概念也是如此),但我只是好奇,因为它增强了我对多重继承的可视化。(顺便说一下,我正在使用MinGW 4.4.0)

初始代码:

class A {
public:
   A() : a(0) {}
   int a;
};
//Edit: adding this definition instead
void f(void* ptrA) {
   std::cout<<((A*)ptrA)->a;
}
//end of editing of original posted code
#if 0
//this was originally posted. Edited and replaced by the above f() definition
void f(A* ptrA) {
   std::cout<<ptrA->a;
}
#endif

对此进行编译并生成目标代码。

我使用的其他编译单元中(在包含上述代码的头文件之后):

class C : public B , public A {
public:
   int c;
}objC;
f(&objC); // ################## Label 1

objC 的内存模型:

//<1> stuff from B
//<2> stuff from B
//<3> stuff from A : int a
//<4> stuff from C : int c

&objC将在上面假设的内存模型中包含 <1> 的起始地址编译器如何/何时将其转换为 <3>?在检查Label 1呼叫期间是否发生?

编辑::

由于 Lable 1 似乎是一个赠送,只是让它对编译器来说更加晦涩难懂。请参阅上面的编辑代码。现在编译器什么时候做,在哪里做?

是的,你说得很对。

要完全了解情况,您必须在两点上知道编译器所知道的内容:

  1. 在标签 1(如您已经确定的那样)
  2. 内部函数 f()

    (1) 编译器知道 C 和 A 的确切二进制布局以及如何从 C* 转换为 A*,并将在调用站点执行此操作(标签 1)

    (2) 然而,在函数 f() 中,编译器只(需要)知道 A*,因此将自己限制为 A 的成员(在本例中为 int a),并且不能混淆特定实例是否是其他任何实例的一部分。

简短回答:如果编译器知道基类和派生类之间的关系,它将在强制转换操作期间调整指针值。

假设类 C 的对象实例的地址位于地址 100。 假设 sizeof(C) == 4。 sizeof(B) 和 sizeof(A) 也是如此。

发生强制转换时,如下所示:

C c;
A* pA = &c;  // implicit cast, preferred for upcasting
A* pA = (A*)&c; // explicit cast old style
A* pA = static_cast<A*>(&c); // static-cast, even better

pA 的指针值将是 c 的内存地址加上 C 中"A"开头的偏移量。 在这种情况下,pA 将引用存储器地址 104,假设 sizeof(B) 也是 4。

所有这些都适用于将派生类指针传递到需要基类指针的函数中。 隐式强制转换将发生,指针偏移量调整也是如此。

同样,对于向下投射:

C* pC = (C*)(&a);

编译器将负责在协同过程中调整指针值。

所有这一切的一个"陷阱"是当一个类在没有完整声明的情况下被向前声明时:

 // foo.h
 class A;  // same as above, base class for C
 class C;  // same as above, derived class from A and B
 inline void foo(C* pC)
 {
      A* pA = (A*)pC; // oops, compiler doesn't know that C derives from A.  It won't adjust the pointer value during assigment
      SomeOtherFunction(pA); // bug! Function expecting A* parameter is getting garbage
 }

这是一个真正的错误!

我的一般规则。 避免旧的"C 样式"强制转换,而倾向于使用 static_cast 运算符,或者仅依靠隐式强制转换而没有运算符来执行正确的操作(对于向上转换)。 如果强制转换无效,编译器将发出错误。