在C ++中,为什么转换指针时地址会发生变化?

时间:2016-01-12 13:09:39

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

以下是代码:

#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的值根据结果而有所不同。我认为它可能与打字有关,但我不确定它是如何工作的。

有没有人有这方面的想法?

3 个答案:

答案 0 :(得分:12)

D继承自B1B2

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

所以你看到的是当你将派生类型的指针强制转换为基类型时,这些部分在内存中的区别。

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

b2d偏移,因为它指向B2的{​​{1}}部分的开头。

答案 1 :(得分:5)

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

说你有这个:

class concrete : public InterfaceA, public InterfaceB

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

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

object layout

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

call *(*objA+0)

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

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

如果你想打电话,会发生什么?基于我们所说的,funb需要位于interfaceB对象的偏移0处。因此有thunk adjustor来调整它以便它指向interfaceB的vtable,以便可以正确地调用funB,再次使用:

call *(*objB+0)

如果你声明这样的话:

concrete *ac = new concrete();
interfaceB *ifb = ac;
你期待什么? concrete现在扮演interfaceB的角色:

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

ifb == ac;

你应该变得正确,因为它们被调整以便描述它们是相同的动态生成的对象。

object layout

答案 2 :(得分:0)

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

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

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

当您将派生类型的指针强制转换为基类型时,转换指针将指向基类子对象。具有不同地址的子对象没有什么令人惊讶的。其中一个子对象与容器具有相同的地址也没有任何意外。

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