我最近尝试通过将几个指针的值打印到控制台来调试一个小程序。第一个是结构的内存地址,其他是其字段的内存地址。代码的精简版本如下:
#include <iostream>
struct testingPointers
{
int i;
float f;
double d;
} test;
int main()
{
std::cout << &test << '\n' << &(test.i) << '\n' <<
&(test.f) << '\n' << &(test.d);
}
输出是:
0x681110
0x681110
0x681114
0x681118
(显然,不同运行的确切值是不同的,但它们相对于彼此总是具有相同的位置)。
我很困惑,因为第一个指针的值 - test
的内存位置 - 与第二个指针的值(test
的第一个字段)相同。这是否意味着对象没有真正唯一的内存地址,并且指向结构或类的指针只是指向其第一个字段?如果是这样,那么语句如何
a.b
a->b
a.b()
如果a
实际上只是它的第一个字段,那么是否有意义,因此没有任何字段或方法?
答案 0 :(得分:6)
对象的地址应始终是该对象中第一个非静态成员的地址。引用标准(C ++ 11-9.2-20):
指向标准布局结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单位),反之亦然。 [注意:因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。
此处提到了标准布局的要求:StandardLayoutType。
这当然可以通过嵌套来应用。对于位字段的第一个成员除之外的类型,该标准没有例外。即:
class X
{
public:
int x;
};
class Y
{
public:
X x;
int y;
};
Y yobj;
按标准&yobj == &yobj.x == &yobj.x.x
。
答案 1 :(得分:5)
类或结构只描述了应该在内存中保存在一起的字段集合,并且它们之间存在一些语义关系以及对它们进行操作的一些操作。在简单的情况下,内存中类类型对象的内容与构成它的成员(以及一些填充)相比没有更多内容。当内存中有testingPointers
个对象时,它实际上只是int
,float
和double
。该类的概念仅用于生成正确的可执行代码 - 它在运行时不存在(至少不是为此目的)。
关于对象是否可以共享内存地址的标准中的重要部分是§1.8/ 6:
除非对象是零字段或零大小的基类子对象,否则该对象的地址是它占用的第一个字节的地址。如果一个是另一个的子对象,或者如果至少一个是零大小的基类子对象并且它们是不同类型的,则不是位字段的两个对象可以具有相同的地址;否则,他们应有不同的地址。
我们可以从中推断,因为成员test.i
是test
的子对象,所以它们可能具有相同的地址。
一旦你深入了解程序中的所有对象,你真正拥有的是标量值和相邻位域的大集合。这些在标准中称为内存位置。这些是真正占据空间的东西。其余的对象都以某种方式由这些组成。
内存位置是标量类型的对象或相邻位域的最大序列,所有这些都具有非零宽度。 [注意:该语言的各种功能(如引用和虚函数)可能涉及程序无法访问但由实现管理的其他内存位置。 - 结束记录]
答案 2 :(得分:3)
只是为了澄清你的困惑:
1)。第一个指针的值 - 测试的内存位置 - 与第二个指针的值相同(第一个测试字段)。
Struct的地址及其第一个字段必须相同,因为struct不是连续的字段集合。
您还可以考虑数组的情况以进一步简化理解,其中数组的地址显然等于数组的第一个元素(字段)。
2)。这是否意味着对象没有真正唯一的内存地址,并且指向结构或类的指针只是指向其第一个字段?
我认为你把它与JAVA混淆了,其中Object总是在Heap上分配。在C ++中,struct / class总是在Stack上分配,除非它是动态内存分配(使用'new'操作符),其中对象在堆中分配,而指针变量(在Stack中)将指向堆中的该对象。因此,在前一种情况下,struct变量将始终具有与其第一个元素(字段)相同的地址。
希望有所帮助。
答案 3 :(得分:1)
内存中的结构只包含其字段串联在一起。根据对齐需要,在结构之前和/或字段之间可能存在填充,但在第一个字段之前通常没有任何额外的“填充”。所以第一个字段和结构本身具有相同的地址。
请记住,在C中,类型仅在编译时存在。