一个物体在记忆中是什么样子的
What does an object look like in memory?
可能重复:
内存中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指向。非虚拟将简单地静态解析并直接使用。
静态成员不是由类创建的对象的成员。静态成员只是一个具有不同作用域的全局变量。
它将列出其成员变量,如果它是多态的,还将有一个虚拟函数表,该表将包括指向其虚拟方法实际关联的函数的指针列表。
静态意味着只有一个副本。
- 松弛原子与无同步情况下的记忆连贯性
- 返回的指向C++对象的链接是什么样的
- 递归函数有效,但无法记忆
- 这些是什么样的错误?即使我不在 Linux 上工作,我也遇到了 Linux 错误
- 如何将记忆应用于此递归函数?
- 共享记忆:让我们谈谈它的特殊性
- 为什么nlohmann不释放记忆
- CPP 中的瓦尔格林德和记忆泄漏:"Conditional jump or move depends on uninitialised values"
- C++17 多态记忆资源不起作用
- 我是否漏了记忆?
- 关于记忆后这种递归关系的时间复杂度
- 记忆栅栏和记忆屏障是一样的吗
- 使用记忆在 C++ 中实现 Knapstack
- 无论如何可以将webm / mp4文件编译/记忆为.exe程序吗?(C++)
- 如何在硬币兑换中添加记忆
- 动态记忆的删除是如何真正起作用的
- 在陈述"Implementation-defined"时,什么样的软件是"Implementation"的一部分?究竟什么是"Implementation"?
- C++遗传,记忆问题
- 虚幻引擎使用什么样的反射?
- boost::ifind_first 会引发什么样的异常?