我最近在处理一个项目时碰到了这个问题,这让我有点困惑。所以我决定写一个测试程序来得到一个明确的答案:
#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?我不太确定这是怎么回事。有人可以把我放在我的位置并解释这里发生了什么吗?
编辑:也许是因为我在私有而不是在对象的构造函数中实例化对象?巴哈,我不知道。
答案 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 ------->
您可以在此处看到A
和int
都具有相同的地址,因为int
是A
类型对象的子对象。如果A
同时包含int
和char
,则可能如下所示:
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
功能,看看会发生什么。