3

请忽略 #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>?是否在检查 call at 期间发生Label 1

编辑::

因为 Lable 1 似乎是一个赠品,只是让它对编译器来说更加模糊。请参阅上面的编辑代码。现在编译器什么时候做,在哪里做?

4

2 回答 2

1

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

假设您的 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”开始的偏移量。在这种情况下,假设 sizeof(B) 也是 4,pA 将引用内存地址 104。

所有这些都适用于将派生类指针传递给需要基类指针的函数。隐式转换将与指针偏移调整一样发生。

同样,对于向下转换:

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 运算符,或者仅依赖隐式转换而不使用运算符来做正确的事情(用于向上转换)。如果转换无效,编译器将发出错误。

于 2012-04-07T18:05:04.397 回答
1

是的,你说得很对。

要完全理解这种情况,您必须知道编译器在两点上知道什么:

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

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

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

于 2012-04-07T18:06:05.373 回答