为什么将子指针向上转换为基类有时会更改指针值?

时间:2016-07-10 22:42:17

标签: c++ pointers casting

我发现了一些似乎与我的问题有关的其他问题,但没有一个问题直接回答(即使the one也有同样的名字)。

我有一个源自两个父母的C ++子类。我创建了该类的一个对象,然后将它的引用转换为它的两个父类。转换值不相同,但仍然是子引用的值。这是代码:

class Mom
{
public:
    int a = 1;
};

class Dad
{
public:
    int b = 2;
};

class Child : public Mom, public Dad
{
public:
    int c = 3;
};

int main()
{
    Child* c = new Child; // 0x00C5A618

    Mom* m = c; // 0x00C5A618
    Dad* d = c; // 0x00C5A61C

    return 0;
}

现在,我猜测通过应用static_cast<>获得的地址是那些实际的子对象。碰巧,Mom是第一个子对象,因此它实际上与Child具有相同的地址。 Dad是第二个子对象,因此它位于不同的地址。

根据我的理解,C风格的演员表,像这样

Mom* m = (Mom*)c;
Dad* d = (Dad*)c;

将应用static_cast<>什么时候这样做是合法的。实际上,当我实际编译并执行该版本时,我会看到与以前相同的行为。

继续这个闪烁的传奇,使用隐式演员,像这样

Mom* m = c;
Dad* d = c;

也表现出相同的行为,我从中推断隐式强制转换也适用于static_cast<>当等效的显式演员是合法的。

向下转回Child会做同样的事情:

Child* k = static_cast<Child*>(d); // 0x00C5A618

k中的值与d中的值不同,已被设置回原来c中的值。

我猜测,在这种情况下,运行时检查是否确实d指向Dad对象的Child子对象。

到目前为止,这一切都是有道理的,但我很惊讶,对于单独派生的层次结构,没有这样的地址调整(如果这是正确的调用它)。也就是说,如果我的层次结构只是Mom - &gt; Dad - &gt; Child,则将对Child的引用转换为指向Mom的指针}或Dad永远不会更改值:

class Mom
{
public:
    int a = 1;
};

class Dad : public Mom
{
public:
    int b = 2;
};

class Child : public Dad
{
public:
    int c = 3;
};

int main()
{
    Child* c = new Child; // 0x00C5A618

    Mom* m = c; // 0x00C5A618
    Dad* d = c; // 0x00C5A618

    Child* k = static_cast<Child*>(d); // 0x00C5A618

    return 0;
}

我原以为它会根据每个附加子类所需的存储大小或四个字节(int的大小)来改变。但是,他们没有。上例中的所有指针cmdk都具有相同的值。

所以,现在我猜测对象是在内存中布局的,Mom对象位于最低地址,Dad对象所需的附加空间接下来,Child最后需要的附加空间。由于DadMomDad地址与Mom地址相同,不是吗?

同样,Child也是Mom,因此其地址也与其自己的Mom子对象的地址相同。

因此...

我认为它的工作方式如下:在内存中,对象相互跟随,在纯粹单独派生的层次结构中,向上转换和向下转换始终产生相同的地址,而在多重派生的层次结构中,某些向上转换产生不同的地址,因为并非所有子对象都将从派生对象的相同地址开始。像这样:

Possible Memory Layout for a Singly Derived Hierarchy

+-------+-----+-----+---+
| Child : Dad : Mom : a |
|       |     +-----+---+
|       |           : b |
|       +-----------+---+
|                   : c |
+-----------------------+


Possible Memory Layout for a Multiply Derived Hierarchy

+-------+-----+---+
| Child : Mom : a |
|       +-----+---+
|       : Dad : b |
|       +-----+---+
|             : c |
+-----------------+

很抱歉我有点絮絮叨叨,但我的问题是这样的事实:对于一个乘法派生对象的某些基类的向上转换会改变引用的值,这是事后投射值的结果是内存中基础对象的地址?

2 个答案:

答案 0 :(得分:4)

是的,你是对的。向上转换指针会给出父子对象的地址,该子对象存储在子类中的某个位置。通常父子对象存储在子对象的开头,因此指向子对象子对象的指针是相同的。

在多重继承的情况下,所有父节点都不可能占用相同的内存(让我们忽略可能的空基优化),因此所有但只有一个基类地址必须与子地址不同。

答案 1 :(得分:2)

  

我猜测,在这种情况下,运行时检查是否确实d指向Dad对象的Child子对象。

不,如果要进行运行时检查,请使用dynamic_cast(但源类型必须是多态的)。使用static_cast,编译器将假设类型正确,否则会发生未定义的行为。

  

我认为它的工作方式如下:在内存中,对象相互跟随,在纯粹单独派生的层次结构中,向上转换和向下转换始终产生相同的地址

这不保证;标准没有具体说明。编译器不必布置基类和成员子对象,以便基类子对象首先出现,尽管在这些情况下您的编译器似乎没有;但是可能存在其他地址可能不同的情况,例如派生类是否包含虚函数而基类不包含。 (在这种情况下,布局可能首先是vptr,然后是基类子对象,然后是成员子对象。)

  

事实上,多重派生对象的某些基类的向上转换会更改引用的值,这是事后转换值是内存中基本对象的地址这一事实的结果吗?