48

可能重复:
内存中 C++ 对象的结构与结构
内存布局 c++ 对象

这可能是一个非常愚蠢的问题,但无论如何我都会问。我很好奇对象在内存中的样子。显然,它必须包含所有成员数据。我假设对象的函数不会在内存中重复(或者我错了?)。内存中的 999 个对象都一遍又一遍地定义相同的函数,这似乎很浪费。如果所有999个对象的内存中只有1个函数,那么每个函数如何知道要修改谁的成员数据(我特别想在底层知道)。是否有一个对象指针被发送到幕后的函数?也许每个编译器都不同?

另外,静态关键字如何影响这一点?对于静态成员数据,我认为所有 999 个对象都将使用完全相同的内存位置存储它们的静态成员数据。这个存储在哪里?我猜静态函数也只是内存中的一个位置,并且不必与实例化对象交互,我想我理解。

4

5 回答 5

26

静态类成员的处理方式几乎与全局变量/函数完全相同。因为它们不依赖于实例,所以没有什么关于内存布局的讨论。

可以想象,每个实例的类成员变量都是重复的,因为每个实例都可以为每个成员变量具有自己的唯一值。

类成员函数在内存中的代码段中只存在一次。在低级别,它们就像普通的全局函数一样,但它们接收一个指向this. 使用 x86 上的 Visual Studio,它是通过ecx使用thiscall调用约定的注册。

当谈到虚函数、多态性时,内存布局变得更加复杂,引入了一个“ vtable ”,它基本上是一堆定义类实例拓扑结构的函数指针。

于 2012-09-11T21:43:47.230 回答
8

正如您所怀疑的,数据成员(字段)是按顺序排列的。这也包括基类的字段。

如果类(或其基类之一)包含任何虚拟方法,则布局通常以 vptr 开头,即指向虚拟表(或 vtable)的指针,该表是指向与该类相关的函数实现的指针表。请注意,这不是标准定义的,但 AFAIK 所有当前的编译器都使用这种方法。此外,对于多重继承,它变得更加复杂,所以我们暂时忽略它。

+-----------+
|  vptr     |  pointer to vtable which is located elsewhere
+-----------+
|  fieldA   |  first member
|  fieldB   |  ...
|  fieldC   |
|  ...      |
+-----------+

字段可以占用比它们各自大小的总和更多的空间,这取决于打包(例如,1 字节打包确保没有间隙,但在性能方面比 4 或 8 字节打包效率低)。

成员函数(非静态)接收指向对象的指针,但如何完成是特定于实现和平台的,例如在 x86 架构上,指针通常通过ecx寄存器传递。这也没有被标准定义。

静态函数类似于全局函数,它们对位于数据段中的静态类字段(类的所有实例共享)进行操作。

于 2012-09-11T21:57:00.260 回答
7

你在这里问了几个问题...

布局

所有非静态成员都像结构一样在内存中组织。如果编译器选择放入任何填充,则可能会有填充。如果您有一个对象数组,它就像一个结构数组

静态成员

显然是分开存放的。一份。

函数调用

课程的幕后发生了一些小魔术。当您调用成员函数时,它与任何其他函数非常相似,只是它具有不同的调用约定。实际上,这会将对象的指针 (this) 插入到参数列表中。

[编辑:函数本身的代码不会与您的对象一起存储——这使您可以做一些有趣的事情delete this,例如在您不再访问刚刚删除的对象的情况下继续执行成员函数]。

当你有重载或多态函数时,事情会变得更加神奇。 这篇文章是我在大约 5 秒内搜索到的解释。我敢肯定还有很多。我从不关心对象调用的内部结构,但知道总是很高兴。

您应该尝试制作一个展示所有这些不同方面的类,并查看每种情况下生成的程序集。我之前在调整一些时间关键的代码时已经这样做了。

于 2012-09-11T21:50:38.570 回答
3

首先要注意的是,在 C++ 中,“对象”一词包括整数之类的东西。

接下来的事情是,结构几乎按照您的期望进行布局。内存中的下一个成员之后的一个成员之间的填充量未定义。

当一个类从另一个类继承时,该类将从它的基类开始,而基类又可以从它自己的基类开始。因此,在单继承的情况下,Derived* 和 Base* 将是相同的值。在基类区域(其成员)之后将依次是派生类的成员,它们之间的填充量未定义。

当一个类从多个基类继承时,情况会有所不同。基本区域按顺序排列在内存中。Base1 后跟 Base2 等...之后派生类的成员依次排列,它们之间的填充量未定义。

如果对象属于 POD 类,则可以保证该类中的第一个成员将位于对象所在的内存位置。这意味着 Class* 和 Class->firstMember* 将是相同的值。我认为这不适用于非 POD 实体。

对于多态类,即具有虚函数的类,将创建一个额外的秘密成员,称为 vtable。标准中的任何内容都不能保证这一点,但几乎是唯一的方法来做到这一点并遵守规则。每个班级都有这个,所以如果你的班级有基地,那么它就会有它的桌子,你会有你的桌子来获得额外的功能。

所有成员函数的名称都将被修改并修改参数以接受this作为第一个参数。当编译器构建东西时,这发生在幕后。vtable 将指向虚函数。非虚拟将简单地静态解析并直接使用。

静态成员不是由类创建的对象的成员。静态成员只是具有不同范围的全局变量。

于 2012-09-11T22:39:13.427 回答
0

它将布局其成员变量,如果它是多态的,还有一个虚函数表,其中将包含指向其虚方法实际关联的函数的指针列表。

静态意味着只有一个副本。

于 2012-09-11T21:38:00.877 回答