【问题标题】:What does an object look like in memory? [duplicate]对象在内存中是什么样子的? [复制]
【发布时间】:2012-09-04 21:19:50
【问题描述】:

可能重复:
Structure of a C++ Object in Memory Vs a Struct
memory layout c++ objects

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

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

【问题讨论】:

  • 这是一个很酷的问题
  • 这是一个非常有趣的问题,但我认为在这里得到了回答:stackoverflow.com/questions/1632600/memory-layout-c-objects 和这里:stackoverflow.com/questions/422830/…
  • 嗯...就像一堆0和1...
  • 这件事有一整本书:Inside the C++ Object Model。考虑一下,无论答案中包含多少信息,还有很多东西需要学习。
  • 我想知道将这个问题标记为重复的罪魁祸首/原因是什么其他问题...这两个“可能的重复”是 not 这个重复的(即“内存中C++对象的结构与结构”并没有解决OP关于如何布局函数的问题,并且“内存布局c++对象”被关闭为“太宽泛”并且没有任何非常有用的答案) ...

标签: c++


【解决方案1】:

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

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

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

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

【讨论】:

  • 为了迂腐,成员函数不会“收到指向this 的指针”。相反,情况正好相反。成员函数有一个隐含的实例参数,在函数体内,this 是一个指向该实例的指针。
  • 即使有这样的描述,说函数接收到this 指针仍然是正确的。这是通过ecx 传递的参数,而不是堆栈。或者我没听懂你的意思。
  • 这只是一种常见的思维捷径,当人们日后试图理解成员函数重载解决方案时,它会给他们带来麻烦。这在这里并不重要,但我想我指出了语言定义成员函数的方式。
  • 如果类包含一个vtable,那么在内存布局的某个地方会有一个指向vtable的指针。如果存在多重继承,则可能存在多个 vtable 和多个指针。重要的是要注意 vtable 不是每个对象的一部分,只是指向它的指针。
【解决方案2】:

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

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

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

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

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

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

【讨论】:

    【解决方案3】:

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

    布局

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

    静态成员

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

    函数调用

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

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

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

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

    【讨论】:

      【解决方案4】:

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

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

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

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

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

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

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

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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

        • 通常每个对象不包含一个函数指针表,而是一个指向表的指针(对于每个多态子类型)。表本身在相同动态类型的所有对象之间共享,从而节省内存。并且这些表不仅包含函数指针,还需要在子类型指针和函数的隐藏this 参数预期的指针类型之间进行调整。
        猜你喜欢
        • 2020-02-11
        • 2017-12-31
        • 2012-01-05
        • 1970-01-01
        • 2018-12-16
        • 1970-01-01
        • 2020-10-03
        • 1970-01-01
        • 2019-02-19
        相关资源
        最近更新 更多