【问题标题】:In C++, why is the address changed when the pointer is converted?在C++中,为什么指针转换时地址会改变?
【发布时间】:2016-04-17 02:57:43
【问题描述】:

以下是代码:

#include <iostream>
using namespace std;

class B1 {
public:

  virtual void f1() {
      cout << "B1\n";
      }
};

class B2 {
public:

  virtual void f1() {
      cout << "B2\n";
      }
};        

class D : public B1, public B2 {
public:
  void f1() {
      cout << "OK\n" ;
  }
};

int main () {

D dd;    
B1 *b1d = &dd;
B2 *b2d = &dd;
D *ddd = &dd;

cout << b1d << endl;
cout << b2d << endl;
cout << ddd << endl;

b1d -> f1();
b2d -> f1();
ddd -> f1();
}

输出是:

0x79ffdf842ee0
0x79ffdf842ee8
0x79ffdf842ee0
OK
OK
OK

这看起来让我很困惑,因为我预计 b1db2d 将与它们都指向 dd 相同。但是,b1db2d 的值根据结果不同。我认为这可能与类型转换有关,但我不确定它是如何工作的。

有人对此有想法吗?

【问题讨论】:

  • This 过去帮了我很多忙。
  • 这是一个退化的例子。

标签: c++ oop pointers multiple-inheritance virtual-functions


【解决方案1】:

D 继承自 B1B2

由于B1 是从第一个继承而来的,因此将首先构造对象的B1 部分,然后创建对象的B2 部分,然后再创建D

因此,当您将派生类型的指针强制转换为基类型时,您所看到的是这些部分在内存中的位置不同。

b1dddd 具有相同的地址,因为它们都指向内存中类的开头。

b2d 是偏移的,因为它指向DB2 部分的开头。

【讨论】:

  • 我明白了。它是 C++ 标准,还是特定于编译器的行为?
  • @hanfeisun 这是标准行为。
【解决方案2】:

您对此的看法部分正确。这个指针指向对象的地址,当然它是类的一部分。更正式地说,这是指向该类的vtable 的指针。但是在您从多个类继承的情况下。那么这应该指向什么?

假设你有这个:

class concrete : public InterfaceA, public InterfaceB

从 interfaceA 和 interfaceB 继承的具体对象必须能够像机器人 interfaceA 和 interfaceB 一样行事(这就是公共的重点:当您继承时)。所以应该有一个“这个调整”,这样才能做到这一点。

通常,在多重继承的情况下,会选择一个基类(例如 interfacea)。在这种情况下,

几乎每个编译器都有一个生成代码的“约定”。例如,为了调用 funa,编译器生成的程序集类似于:

call *(*objA+0)

其中+0部分是函数在vtable中的偏移量。

编译器需要在编译时知道这个方法的(funa)偏移量。

现在如果你想调用 funb 会发生什么?根据我们所说的, funb 需要位于 interfaceB 对象的偏移量 0 处。所以有thunk adjustor 来调整它,使它指向interfaceB 的vtable,这样funB 就可以被正确地调用,再次使用:

call *(*objB+0)

如果你这样声明:

concrete *ac = new concrete();
interfaceB *ifb = ac;

你期望什么?具体现在扮演interfaceB的角色:

如果我没记错的话,你可以打印 ifb 和 ac(它们是指针),并验证它们指向不同的地址,但如果你检查它们是否相等:

ifb == ac;

您应该是正确的,因为它们被调整以描述它们是同一个动态生成的对象。

【讨论】:

  • 谢谢...解释得很好。 :)
【解决方案3】:

C++ 标准规定对象的大小必须为at least 1(字节)。两个单独的对象不能具有相同的地址

子对象可以与包含它的对象具有相同的地址。通常,没有子对象可以与另一个子对象具有相同的地址,因为它们不直接相关。因此(通常)至多一个子对象可以与容器对象具有相同的地址。

在这种情况下,D 的实例包含 2 个子对象。它们都是基类子对象。其中一个与容器对象具有相同的地址,而另一个则没有。

当您将派生类型的指针转​​换为基类型时,转换后的指针将指向基类子对象。具有不同地址的子对象并不奇怪。与容器具有相同地址的子对象之一也不足为奇。

上面段落中的规则实际上有一个例外。空基类子对象不需要有任何大小。这被称为empty base optimization。您的基类不是空的,因此不适用。

【讨论】:

  • 它们不是空的,因为每个都有一个 vtable 指针。该标准不要求这些 vtable 指针存在,但实际实现确实需要它们。
  • @JSF 好吧,这是一个与标准无关的实现细节。在使用 vtable 的实现中,空基优化在这里是不可能的。我将不得不检查标准中如何定义 empty 类。
  • @JSF 我找不到空类的定义,但我现在认为它是指{};,而不是像我最初想的那样没有子对象的类。因此,根据该定义,OP 的基础确实不是空的。
猜你喜欢
  • 1970-01-01
  • 2020-03-19
  • 1970-01-01
  • 1970-01-01
  • 2016-05-26
  • 1970-01-01
  • 2020-12-10
  • 2018-03-01
  • 1970-01-01
相关资源
最近更新 更多