【问题标题】:Why does upcasting a child pointer to a base class sometimes change the pointer value?为什么将子指针向上转换到基类有时会更改指针值?
【发布时间】:2016-11-12 19:38:36
【问题描述】:

我发现了一些似乎与我的问题有关的其他问题,但没有人直接回答它(甚至 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 是否确实指向Child 对象的Dad 子对象。

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

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 |
+-----------------+

抱歉,我有点啰嗦,但我的问题是:向上转换到乘法派生对象的某些基类会改变引用的值,这是因为后转换值基对象在内存中的地址是?

【问题讨论】:

    标签: c++ pointers casting


    【解决方案1】:

    是的,你是对的。向上转换一个指针给出了存储在子类中某处的父子对象的地址。通常父子对象存储在子对象的开头,所以指向子对象和基子对象的指针是一样的。

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

    【讨论】:

      【解决方案2】:

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

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

      我认为它的工作原理是这样的:在内存中,对象彼此跟随,因此,在纯粹的单一派生层次结构中,向上转换和向下转换总是产生相同的地址

      这不能保证;该标准未指定。编译器不必布置基类和成员子对象,以便基类子对象排在第一位,尽管您的编译器似乎在这些情况下会这样做;但在其他情况下,地址可能不同,例如派生类包含虚函数而基类不包含。 (在这种情况下,布局可能是先vptr,然后是基类子对象,然后是成员子对象。)

      向上转换到多重派生对象的某些基类会改变引用的值是因为转换后的值是内存中基对象的地址吗?

      是的。

      【讨论】:

      • 那是一些令人费解的东西!作为一个老 C 程序员,转换指针会产生与指针本身不同的值的想法很难接受。进一步证明不能仅仅假设 C++ 中的类 C 代码的行为方式与真正的 C 中的行为方式相同。
      猜你喜欢
      • 2015-08-12
      • 1970-01-01
      • 1970-01-01
      • 2016-07-26
      • 1970-01-01
      • 2013-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多