一个物体在记忆中是什么样子的

What does an object look like in memory?

本文关键字:记忆 什么样 一个      更新时间:2023-10-16

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

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

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

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

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

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

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

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

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

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

字段占用的空间可能比其单个大小的总和更多,这取决于封装(例如,1字节封装确保没有间隙,但在性能方面不如4或8字节封装有效)。

成员函数(非静态)接收指向对象的指针,但如何做到这一点取决于具体的实现和平台,例如在x86体系结构上,指针通常通过ecx寄存器传递。该标准也未对此进行定义。

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

您在这里问了一些问题。。。

布局

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

静态成员

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

函数调用

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

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

当您拥有重载或多态函数时,事情会变得更加神奇。这篇文章是我在谷歌上搜索了大约5秒钟的解释。我相信还有更多。我从来没有太关心对象调用的内部,但知道它总是很好的。

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

首先要注意的是,在C++中,术语"对象"包括整数之类的东西。

接下来的事情是,结构的布局和你所期望的差不多。内存中一个成员跟在下一个成员后面,其间有未定义的填充量。

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

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

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

在多态类的情况下,即那些具有虚拟函数的类,将创建一个额外的秘密成员,称为vtable。标准中没有任何东西可以保证这一点,但这几乎是实现这一点并遵守现有规则的唯一方法。每个类都有这个,所以如果你的类有基,那么它就会有它的表,你也会有你的表来执行其他函数。

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

静态成员不是由类创建的对象的成员。静态成员只是一个具有不同作用域的全局变量。

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

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