【问题标题】:C++: Pointer contains different address after being passedC ++:指针在传递后包含不同的地址
【发布时间】:2015-03-10 22:11:33
【问题描述】:

所以我有一些这样的代码:

#include <iostream>

using namespace std;

class Base1 {};
class Base2 {};

class A
{
public:
    A() {}
    void foo(Base2* ptr) { cout << "This is A. B is at the address " << ptr << endl; }
};

A *global_a;

class B : public Base1, public Base2
{
public:
    B() {}
    void bar()
    {
        cout << "This is B. I am at the address " << this << endl;
        global_a->foo(this);
    }
};

int main()
{
    global_a = new A();
    B *b = new B();

    b->bar();

    system("pause");
    return 0;
}

但这是我用 Visual Studio 2013 编译后得到的输出:

我是 B,我在地址 0045F0B8

这是A。B在地址0045F0B9

按任意键继续。 . .

有人能解释一下为什么地址不同吗?

【问题讨论】:

  • 有人能解释一下为什么这被否决了吗?我认为这是一个合理的问题,考虑到为什么会发生这种情况并不是很明显,而且我相信很多人可以从阅读此处发布的答案中受益。
  • 我可以提供部分解释:这个问题确实有道理,而且还很有趣。但是,SO 上的某些用户却不是(如果您可以将这些特征归因于人类)。这里有很多讨厌的挑剔者认为他们真的是炙手可热的编程天才。尽量不要把他们当回事,大多数 SO 社区都是由积极的用户组成,他们愿意提供帮助。你会得到我的 +1,下面的两个答案也是如此。

标签: c++ pointers visual-studio-2013 multiple-inheritance memory-address


【解决方案1】:

0x0045F0B8 是完整的B 对象的地址。 0x0045F0B9B 对象的Base2 子对象的地址。

一般来说,完整对象的地址可能与基类子对象的地址不同。在您的情况下,B 对象的布局可能如下:

+---+-------+
|   | Base1 | <-- 0x0045F0B8
| B |-------+
|   | Base2 | <-- 0x0045F0B9
+---+-------+

每个基类占用一个字节,Base1 布局在Base2 之前。指向完整B 的指针指向开头,即0x0045F0B8,但指向Base2 的指针指向完整B 对象内部Base2 子对象开始的地址,即0x0045F0B9.

但是,当我使用 g++ 4.8 在我的系统上编译您的程序时,我在两行中都打印了相同的地址。这大概是因为允许实现根本不为空基类分配空间(所谓空基类优化)和两个基类子对象@987654336 @ 和Base2 都位于B 对象的最开头,不占用空间,并与B 共享它们的地址。

【讨论】:

  • 有什么方法可以向 B 的实例询问其完整地址吗?我想在 A 中有一个函数来比较两个 B 指针的地址,看看它们是否指向同一个实例,我在尝试实现它时遇到了这个问题。
  • @android927 dynamic_cast&lt;B *&gt;(ptr)
  • @MattMcNabb 这行不通,因为在我的例子中,ptr 是一个指针,它可以指向从 Base2 继承的各种类中的任何一个。如果我在我比较的两个指针上都做了dynamic_cast&lt;Base2 *&gt;(ptr),它会起作用吗?
  • @android927 dynamic_cast&lt;void*&gt; 可能会起作用——它会为您提供完整对象的地址。但是您需要确保实际指向的所有内容都是多态的(例如,通过确保您正在执行此操作的所有类都具有虚拟析构函数)。
  • 为了可靠地比较两个指针,它们必须指向相同的数据类型,以确保对齐等相同。只有这样,指向同一对象的两个指针才会正确排列。您无法将B*Base1*Base2* 进行比较,但您可以将B*B*Base1*Base1*Base2* 与a 进行比较Base2* 等。在此示例中,如果将 B*BaseX* 进行比较,编译器可能会在执行比较之前将 B* 自动向下转换为 BaseX*
【解决方案2】:

B 派生自Base1Base2,因此它包含Base1Base2 包含的所有数据,以及B 在它们之上添加的所有数据。

B::bar() 将指向自身 Base2 部分的指针传递给 A::for(),而不是自身的 B 部分。 B::bar() 正在打印B 部分的根地址,而A::foo() 正在打印Base2 部分的根地址。您正在传递同一个对象,但它们在该对象中是不同的地址:

如果B 没有添加任何新数据,则其基地址可能与其最近祖先的根地址相同(由于empty base optimization):

不要依赖它。编译器可能会在类之间添加填充,例如:

始终将各个部分视为独立的(因为它们在逻辑上是独立的)。仅仅因为 B 派生自 Base2 并不能保证指向同一个对象的 B* 指针和 Base2* 指针将指向同一个内存地址。

如果您有一个Base2* 指针并且需要访问它的B 数据,请使用dynamic_cast(或static_cast,如果您确定该对象是B)以确保正确的B*指针。您可以从B* 向下转换为Base2* 而无需转换(这就是为什么B::bar() 能够将this - 一个B* - 传递给A::foo() 当它期望Base2* 作为输入时)。给定一个B* 指针,您始终可以直接访问它的Base2 数据。

【讨论】:

  • 但是当我在 codeingground 编译这段代码时,输​​出的地址是相同的。自己试试吧:tutorialspoint.com/compile_cpp11_online.php
  • @android927:这不是每个平台上的每个编译器的保证,甚至在同一个程序中结构相似的类中也是如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-09
  • 2016-02-08
  • 2015-09-17
  • 2020-01-09
  • 2015-05-19
  • 1970-01-01
  • 2016-06-20
相关资源
最近更新 更多