其他对象中的对象是否真的共享同一个地址?

时间:2013-01-23 13:42:26

标签: c++ object memory

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

#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层(对象)怎么办?还是10,000?我不太确定这是怎么回事。有人可以把我放在我的位置并解释这里发生了什么吗?

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

4 个答案:

答案 0 :(得分:12)

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

  

如果一个是另一个的子对象,或者如果至少有一个是零大小的基类子对象并且它们的类型不同,那么两个不是位字段的对象可以具有相同的地址;否则,他们应有不同的地址。

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

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

  

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

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

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

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

class A
{
  int x;
};

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

A a1;
A a2;

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

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

如果我们知道A只包含int - 也就是说,A对象实际上 而不是int(除了可能是一些填充) - 然后我们知道如果我们把它分解得更多,内存实际上看起来像这样:

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

您可以在此处看到Aint都具有相同的地址,因为intA类型对象的子对象。如果A同时包含intchar,则可能如下所示:

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

我们知道char的地址高于int,因为标准再次说明了这一点:

  

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

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:0)

我发现你所观察到的东西并不奇怪。一个对象由其成员一个接一个地写在存储器中,因此对象的地址和它的第一个成员的地址是相同的。无论你拥有多少层,都会像那样。

答案 3 :(得分:0)

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

作为测试,您可以向virtual添加layer2功能,看看会发生什么。