使用reinterpret_cast解释对象地址

5
以下代码输出结果为136。但我无法理解前两个地址比较是如何相等的。希望能得到任何帮助来理解这一点。谢谢。
#include <iostream>

class A
{
public:
    A() : m_i(0){ }
protected:
    int m_i;
};

class B
{
public:
    B() : m_d(0.0) { }
protected:
    double m_d;
};

class C : public A, public B
{
public:
    C() : m_c('a') { }
private:
    char m_c;
};

int main( )
{
 C d;
 A *b1 = &d;
 B *b2 = &d;

const int a = (reinterpret_cast<char *>(b1) == reinterpret_cast<char *>(&d)) ? 1 : 2;
const int b = (b2 == &d) ? 3 : 4;
const int c = (reinterpret_cast<char *>(b1) == reinterpret_cast<char *>(b2)) ? 5 : 6;

std::cout << a << b << c << std::endl;

return 0;
}

1
你为什么要在这里使用 reinterpret_cast?你期望使用char*作为指针类型在你的架构上会有什么效果? - Benjamin Bannier
这不是来自实时应用程序。这是来自C++测试。我只想了解reinterpret_cast在这种特定情况下如何解释地址。 - irappa
3个回答

1
当您使用多重继承时,例如在您的示例中,第一个基类和派生类共享相同的基地址。您从中继承的其他类按照所有前面类的大小排列在偏移量上。比较的结果是真的,因为db1的基地址相同。
在您的情况下,如果A的大小为4个字节,则B将从A+4个字节的基地址开始。当您执行B *b2 = &d;时,编译器计算偏移量并相应地调整指针值。
当您执行b2 == &d时,在比较之前对d执行了从类型'C'到类型'B'的隐式转换。这种转换像赋值一样调整指针值的偏移量。

更新了答案,包括为什么会得到true的信息。 - Captain Obvlious

0

通常情况下,派生类(例如这里的C)在内存中的布局是以其两个基类(AB)开头,因此类型为C的实例的地址与其第一个基类(即A)的实例地址相同。


0

在这种继承方式中(不涉及virtual),每个C的实例将具有以下布局:

  • 首先,将是所有A的成员(仅为m_i,一个4字节整数)
  • 其次是所有B的成员(仅为m_d,一个8字节双精度浮点数)
  • 最后是C本身的所有成员,即一个字符(1字节,m_c

当您将指向C实例的指针转换为A时,因为A是第一个父类,所以不会进行地址调整,两个指针的数值将相同。这就是为什么第一个比较评估为true的原因。(请注意,对指针执行reinterpret_cast<char *>()永远不会导致调整,因此它始终给出指针的数值。将其转换为void *将具有相同的效果,并且可能更安全用于比较。)

将指向C实例的指针转换为B会导致指针调整(4个字节),这意味着b2的数值与&d的数值不相等。然而,当您直接比较b2&d时,编译器会自动生成将&d强制转换为B *的代码,从而通过调整数值4个字节。这就是第二个比较也评估为true的原因。
第三个比较返回false,因为如前所述,将指向C实例的指针转换为AB将产生不同的结果(转换为A *不进行调整,而转换为B *则会进行调整)。

布局取决于编译器。你所描述的是典型的,但并没有在任何地方被规定。 - user207421
@EJP 你说得对!但是OP正在寻求有人解释他在编译代码时看到的行为。而我正在做这件事。我并不声称这是标准规定的;我只是解释了他的程序输出为什么会是这样。 - yzt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接