如果正确完成,请忽略#include部分。这也可能是特定于实现的(但是vtable的概念也是如此)但我很好奇,因为它增强了我对多重继承的可视化。 (顺便说一句,我使用的是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
将包含起始地址&lt; 1&gt;在上面假设的记忆模型中
编译器如何/何时将其转换为&lt; 3&gt;?是否在检查Label 1
的电话时发生了什么?
修改::
因为Lable 1似乎是一个赠品,只是让它对编译器来说更加模糊。请参阅上面的编辑代码。现在编译器什么时候做?在哪里?
答案 0 :(得分: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的存储器地址加上“A”从C开始的偏移量。在这种情况下,假设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
}
这是一个真正的错误!
我的一般规则。使用static_cast运算符避免使用旧的“C风格”强制转换,或者只依靠隐式强制转换而不使用运算符来执行正确的操作(对于upcast)。如果转换无效,编译器将发出错误。
答案 1 :(得分:1)
是的,你是完全正确的。
要完全理解这种情况,你必须知道编译器在两点上知道什么:
内部函数f()
(1)编译器知道C和A的确切二进制布局以及如何从C *转换为A *,并且将在调用站点(标签1)执行此操作
(2)但是,在函数f()内部,编译器只需要(需要)知道A *,因此将自身限制为A的成员(在本例中为int a),不能混淆是否特定实例是其他任何内容的一部分。