【问题标题】:Why does static_cast gives different memory locations for same object?为什么 static_cast 为同一个对象提供不同的内存位置?
【发布时间】:2015-08-08 08:10:52
【问题描述】:

我的代码

class Parent { int a; };
class Child { int b; };
struct GrandChild : public Parent, public Child { int a, b, c; };

int main() {
    GrandChild GC;
    std::cout << "GrandChild's address is at : " <<&GC<<endl;
    std::cout << "Child's address is at : " <<static_cast<Child*>(&GC)<<endl;
    std::cout << "Parent's address is at : " <<static_cast<Parent*>(&GC)<<endl;

}

输出:

GrandChild's address is at : 0077F6F8
Child's address is at : 0077F6FC
Parent's address is at : 0077F6F8

为什么在static_cast之后内存位置会出现上面这样的不一致?

【问题讨论】:

  • 有趣……今天学到了一些新东西,感谢您提出的真棒问题!
  • 顺便说一句,这些类型名称极具误导性!
  • @LightnessRacesinOrbit: 对不起,我不知道怎么做 :(
  • @InQusitive:将基类称为“父类”和将派生类称为“子类”是很常见的。由于您已经重复使用这些术语来表示生物学家庭关系(并且不准确地,在那个方面),例如“孩子”类型不是从“父母”类型派生的示例,这令人困惑。

标签: c++ inheritance memory-address static-cast


【解决方案1】:

GrandChild 派生自 ParentChild。因此,内存中的GrandChild 对象由内存中的Parent 对象和Child 对象组成。

&amp;GC自身返回GrandChild对象整体的内存地址

static_cast&lt;Parent*&gt;(&amp;GC) 返回GrandChild 对象内Parent 部分的起始地址。

static_cast&lt;Child*&gt;(&amp;GC) 返回GrandChild 对象内Child 部分的起始地址。

在您的情况下,Grandchild 首先派生自Parent,因此Parent 部分与GrandChild 的内存块的开头对齐。然后Child 部分跟随Parent 部分。这是一个插图来说明:

【讨论】:

  • 这张照片告诉我我在 cmets 中哪里错了,谢谢。
【解决方案2】:

&amp;GCGrandChild 对象GC 的地址。 static_cast&lt;Child*&gt;(&amp;GC)GCChild 子对象 的地址。而static_cast&lt;Parent*&gt;(&amp;GC)GCParent 子对象 的地址。

在您的特定实现中,GrandChild 对象似乎以Parent 子对象开头,然后是Child 子对象,因此Parent 子对象的地址与完整的GrandChild 对象的地址相同地址,但Child 子对象的第一个字节不是完整的GrandChild 对象的第一个字节,所以它的地址更高。但是,您不能依赖这种可移植的行为;不同的实现可能会以不同的顺序分配基类和成员子对象,甚至不需要在不同的类之间保持一致。

【讨论】:

  • 为什么在这个例子中ParentGrandChild的内存开始是相似的?它们不应该按内存升序排列吗?它们如何共享同一个内存块?
  • @VictorPolevoy 一个GrandChild 对象本身包含一个Parent 对象和一个Child 对象。碰巧在 OP 的编译器上,Parent 对象被分配在 GrandChild 对象的最开头。它们共享一个内存块,因为Parent 对象确实是GrandChild 对象的一部分
  • 我的意思是,在我看来,内存中的类应该这样对齐:Base1, Base2, Base 3, ... 与它们自己的数据,正如here 所描述的那样。所以,根据clang,Parent::a应该是当前对象布局的开始,GrandChild::a应该是GrandChild对象内存的开始,没有任何交集。
  • @VictorPolevoy:您似乎没有理解基类对象实际上是派生类对象的一部分。 Parent 子对象(包括它的 a 成员)与在中声明的 3 个成员(abc)一样,是 GrandChild 对象数据的一部分类定义。您似乎期望GrandChild 对象的地址不在GrandChild 对象的开头(这将与Parent 子对象的地址一致),而是在中间的某个位置(其中位于GrandChild 类中声明的成员)。
  • @VictorPolevoy:哦,没有任何对象与彼此的记忆相交。 GrandChild 对象的布局类似于Parent 子对象,然后是Child 子对象,然后是成员abc
【解决方案3】:

在您的示例中,即使它是实现定义的也是可以预测的(但允许实现者为简单的情况选择直接的解决方案:-))

这是您的对象的内存表示(从地址推导出来,而不是从标准推导出来!):

Parent::a (int = 4 bytes)
Child::b (int = 4 bytes)
GrandChild::a
GrandChild::b
GrandChild::c

这是因为您的声明:GrandChild 首先从 Parent 继承,然后从 Child 继承。通过这种表示,Parent 的地址与 GrandChild 的地址相同,Child'地址大 4 是有意义的。

另请注意,GrandChild::a 不是 Parent::a...

【讨论】:

  • 为什么Parent的地址和GrandChild的地址一样,我不明白。请澄清
  • @VictorPolevoy - 它可以是一样的,但它不是必须是。请注意,Parent::a alsoParentGrandChild 具有相同的地址。 :-) 通常,允许不同类型的对象具有相同的地址,因为它们无论如何都不能相互混淆。 相同类型的两个对象必须有不同的地址。
  • @VictorPolevoy:因为这是 4 字节 Parent 子对象开始的地址,整个 n 字节 GrandChild 对象的地址开始。唯一“有趣”的地方是 4 字节 Child 子对象的起始地址不同。
【解决方案4】:

看来这取决于 GrandChild 从 Parent 和 Child 继承的顺序。

我得到了以下序列

struct GrandChild : public Parent, public Child { int a, b, c; };

输出 1:孙子的地址与父母的地址相同

GrandChild's address is at : 0x22fecc
Child's address is at : 0x22fed0
Parent's address is at : 0x22fecc

关于将 GrandChild 从 Child 和 Parent 继承的顺序更改为

struct GrandChild :  public Child , public Parent { int a, b, c; };

输出 2:GrandChilds 的地址与 Child 的地址相同

GrandChild's address is at : 0x22fecc
Child's address is at : 0x22fecc
Parent's address is at : 0x22fed0

【讨论】:

    【解决方案5】:

    这里的其他答案已经完美地完成了这项工作,但无论如何让我打扰一下。

    首先,Parent 不是父级,而 Child 不是 Parent 的子级;因为它不是从父级继承的。 Parent 和 Child 都是 GrandParent 的继承,而 GrandParent 不是...祖父母!

    其次,回答您的问题,您观察到的效果并不是真正的不一致,而是 C++ 如何实现多态性。 (我不认为其他答案已经清楚了)。

    PolyMorphism(poly = many, morphism = morphing into)是一种面向对象的编程概念,它允许一个对象在运行时能够变形为许多不同的对象。这允许对象以不同的方式表现。例如,它现在可以是狗,然后是猫,然后是鬼。

    在大多数面向对象语言、C++、Java 等中,多态是如何通过指针算术(增量)和继承来实现的。如果 std::string 可以变形为 std::vector 会很酷,但是因为它们不共享继承,所以在语法上是不可能的。

    但是,对于您的 Parent 类,它可以变形为 GrandParent 或任何其他派生类。同样,GrandParent 可以演变为 Parent 或 Child。

    多态的另一个名称是多态转换!!!

    根据您的问题,了解多态性是一种 C++ 转换类型很重要。 C++ 转换旨在准确和无损。例如,您可以将 int 转换为 char 并返回。数据的完整性得到维护!同样,如果我从 GrandParent 转换为 Child(这是 static_cast 所做的);最好将对象的指针设置为Child的地址。如果我们转换为 child 并继续从 GrandParent 读取,那么我们将读取 WRONG DATA。在您首先从 Parent 继承的情况下,我们最终会读取存储在 Parent 中的 a 和 b 值。

    更糟糕的是,这种转换是错误的。如果我们转换为 Child,并且 child 有一个特殊的函数,例如 getString,如果我们从 GrandChild 的起始地址调用这个函数,BOOM!我们肯定会遇到运行时崩溃!

    希望您喜欢这个视频并学到了一些东西。如果您想要更多免费视频,请记得点赞和订阅。谢谢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-17
      • 2019-05-23
      • 2014-05-20
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      • 2021-05-16
      • 2014-02-13
      相关资源
      最近更新 更多