5

我最近在做一个项目时遇到了这个问题,这让我有点困惑。所以我决定写一个测试程序来得到一个确定的答案:

#include <iostream>

using namespace std;

class layer3{
public:
    layer3(){}
    ~layer3(){}     
private:

};


class layer2{
public:
    layer2(){}
    ~layer2(){}

    layer3* GetBAddress(){return &b;}
private:
    layer3 b;
};


class layer1{
public:
    layer1(){}
    ~layer1(){}

    //returns the address of a, which is a 'layer2' object
    layer2* GetaAddress(){return &a;}
    //returns the address of b, which is is a layer 3 object
    layer3* GetDeepBAddress(){return a.GetBAddress();}
private:
    layer2 a;

};

int main(){

    layer1 t;
    cout << &t << "  : layer 1's address" << endl;
    cout << t.GetaAddress() <<  "  : layer 2's address" <<endl;
    cout << t.GetDeepTLAddress() <<  "  : layer 3's address" <<endl;

}

该程序创建 3 个对象。layer2 在 layer1 内部创建,layer3 在 layer2 内部创建。然后我调用来获取 layer1、layer2 和 layer3 的地址,就像之前发生的那样,这是输出:

$ ./a.exe
0x28ac4f  : layer 1's address
0x28ac4f  : layer 2's address
0x28ac4f  : layer 3's address

这三个对象如何共享内存中的同一个位置?如果我将这个程序扩展为 50 层(对象)会怎样?还是一万?我不太确定这怎么可能。有人可以让我代替我解释这里发生了什么吗?

编辑:也许是因为我在私有而不是在对象的构造函数中实例化了对象?呸,我不知道。

4

4 回答 4

12

最明确的答案是 C++ 标准给出的答案:

如果一个对象是另一个的子对象,或者如果至少一个是大小为零的基类子对象并且它们属于不同类型,则两个不是位域的对象可能具有相同的地址;否则,它们应具有不同的地址。

也就是说,如果一个对象是另一个对象,它们可能具有相同的地址。

在 C++11 中,标准布局结构(尽管它的名称也可能是class)对象的第一个成员保证与对象本身具有相同的地址:

指向标准布局结构对象的指针,使用 a 进行适当转换reinterpret_cast,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。

由于您的类都是标准布局,因此您观察到的行为由 C++11 保证。

在 C++03 中,规则类似,但适用于POD 结构类型,而不是标准布局结构类型。但是,您的类不是POD 结构类型,因为它们具有用户定义的析构函数。因此,您在此处看到的行为不受C++03 的保证。

那么为什么会发生这种情况呢?好吧,一个类实际上是一种将一些数据组合在一起并提供对该数据的操作的方法。考虑一个只包含这样的int类:

class A
{
  int x;
};

所有这个类是由那个组成的int。当你创建一个 type 的对象时A,你真正要做的就是为其内部分配足够的空间并初始化它们(或者在这种情况下,不初始化它们)。假设我们创建了两个实例A

A a1;
A a2;

我们的记忆中有什么?你可以想象它看起来像这样:

   a1     a2
┌──────┬──────┐┄┄
│  A   │  A   │
└──────┴──────┘┄┄
Memory ------->

如果我们知道A它只包含一个int- 也就是说,一个A对象实际上只不过是一个int(可能除了一些填充) - 那么我们知道如果我们进一步分解它,内存实际上看起来像这样:

   a1     a2
┌──────┬──────┐┄┄
│ int  │ int  │
└──────┴──────┘┄┄
Memory ------->

您可以在这里看到Aint都具有相同的地址,因为int是类型对象的子对象A。如果A同时包含 anint和 a char,它可能看起来像这样:

       a1            a2
┌──────┬──────┬──────┬──────┐┄┄
│ int  │ char │ int  │ char │
└──────┴──────┴──────┴──────┘┄┄
Memory ------->

我们知道这char将有一个更高的地址,int因为标准再次这样说:

分配具有相同访问控制(第 11 条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。

请注意,子对象不一定与包含在其中的对象共享其地址,即使它是第一个。这完全取决于编译器。

于 2013-01-23T13:47:39.940 回答
3

在内存中,一个类的实例(通常)占用其数据成员的空间(加上一个虚拟表指针或其他实现定义的位)。

从纯粹的数据成员的角度来看,一个实例layer1只包含一个实例layer2,因此存储在其中的第一个元素(与实例本身layer1具有相同地址的元素)自然是实例。相同的推理适用于和。layer1layer2layer2layer3

于 2013-01-23T13:47:48.307 回答
0

我没有发现你所观察到的任何奇怪之处。一个对象是由其成员在内存中一个接一个地写入的,因此一个对象的地址和它的第一个成员的地址是相同的。不管你有多少层,都会这样。

于 2013-01-23T13:46:59.603 回答
0

这就是对象在内存中的存储方式。C++ 只对成员对象的排序方式做出一些保证——很多东西取决于编译器——对齐、切换成员(受限制)。为什么不需要添加内存或偏移成员?如果地址不同,您将拥有未使用内存的对象。

作为测试,您可以添加一个virtual函数layer2并查看会发生什么。

于 2013-01-23T13:47:05.943 回答