在C ++中,内存中的类函数放在哪里?

时间:2009-03-15 22:10:44

标签: c++ memory dynamic

我试图通过创建大量对象来了解我将会遇到什么样的内存。我知道每个对象 - 在创建时 - 将在HEAP中为成员变量提供空间,并且我认为属于该类型对象的每个函数的所有代码都存在于内存中的代码段中 - 永久。

是吗?

因此,如果我用C ++创建100个对象,我可以估计我需要空间用于对象拥有的所有成员变量乘以100(这里可能的对齐问题),然后我需要空间在代码段中为该类型对象的每个成员函数的代码的单个副本(不是代码的100个副本)。

虚拟函数,多态,继承因素以某种方式进入吗?

动态链接库中的对象怎么样?我假设dll获得了自己的堆栈,堆,代码和数据段。

简单示例(可能在语法上不正确):

// parent class
class Bar
{
public:
    Bar()  {};
    ~Bar() {};

    // pure virtual function
    virtual void doSomething() = 0;

protected:
    // a protected variable
    int mProtectedVar;
}

// our object class that we'll create multiple instances of
class Foo : public Bar
{
public:
    Foo()  {};
    ~Foo() {};

    // implement pure virtual function
    void doSomething()          { mPrivate = 0; }

    // a couple public functions
    int getPrivateVar()         { return mPrivate; }
    void setPrivateVar(int v)   { mPrivate = v; }

    // a couple public variables
    int mPublicVar;
    char mPublicVar2;

private:
    // a couple private variables
    int mPrivate;
    char mPrivateVar2;        
}

关于动态分配Foo类型的对象需要多少内存,包括代码空间和所有变量?

9 个答案:

答案 0 :(得分:27)

“每个对象 - 在创建时 - 将在HEAP中为成员变量提供空间”并不一定如此。您创建的每个对象都会在某处为其成员变量占用一些非零空间,但是您可以在何处分配对象本身。如果对象具有自动(堆栈)分配,则其数据成员也是如此。如果对象是在免费存储(堆)上分配的,那么它的数据成员也是如此。毕竟,除了数据成员之外,对象的分配是什么?

如果堆栈分配的对象包含一个指针或其他类型,然后用于在堆上进行分配,则无论对象本身的创建位置如何,都会在堆上进行分配。

对于具有虚函数的对象,每个对象都将分配一个vtable指针,就像它是类中显式声明的数据成员一样。

对于成员函数,这些函数的代码在可执行映像中的位置方面可能与自由函数代码没有区别。毕竟,成员函数基本上是一个自由函数,隐含的“this”指针作为它的第一个参数。

继承不会改变任何东西。

我不确定你对DLL获取自己的堆栈的意思。 DLL不是程序,并且不需要堆栈(或堆),因为它分配的对象总是在具有自己的堆栈和堆的程序的上下文中分配。在DLL中存在代码(文本)和数据段确实有意义,尽管我不是在Windows上实现这些东西的专家(我假设你使用的是你的术语)。

答案 1 :(得分:6)

代码存在于文本段中,并且基于类生成了多少代码相当复杂。没有虚拟继承的无聊表面在文本段中表面上只有一个代码用于每个成员函数(包括在省略时隐式创建的那些,例如复制构造函数)。正如您所说,任何类实例的大小通常是成员变量的总和大小。

然后,它变得有些复杂。一些问题是......

  • 如果需要或被指示,编译器可以使用内联代码。因此即使它可能是一个简单的函数,如果它在许多地方使用并被选择用于内联,则可以生成大量代码(遍布程序代码)。
  • 虚拟继承会增加每个成员的多态性大小。 VTABLE(虚拟表)使用虚方法隐藏类的每个实例,其中包含运行时调度的信息。如果您有许多虚函数或多个(虚拟)继承,则此表可能会变得非常大。澄清:VTABLE是每个类,但VTABLE的指针存储在每个实例中(取决于对象的祖先类型结构)。
  • 模板可能会导致代码膨胀。使用带有一组新模板参数的模板化类可以为每个成员生成全新的代码。现代编译器尽可能地尝试崩溃,但这很难。
  • 结构对齐/填充可能导致简单的类实例比预期的更大,因为编译器填充了目标体系结构的结构。

编程时,使用sizeof运算符确定对象大小 - 从不硬编码。在估算大型实例组的成本时,请使用“成员变量大小的总和+某些VTABLE(如果存在)”的粗略度量,并且不要过于担心代码的大小。稍后进行优化,如果任何非显而易见的问题再次发生意义,我会感到非常惊讶。

答案 2 :(得分:2)

虽然这方面的某些方面依赖于编译器供应商。在大多数称为“text”的系统上,所有编译的代码都会进入内存的一部分。这与堆和堆栈部分分开(第四部分,'data',包含大多数常量)。实例化类的许多实例仅为其实例变量而不是其任何函数引发运行时空间。如果您使用虚拟方法,您将获得为虚拟查找表(或使用其他概念的编译器的等效内容)预留的额外但小的内存,但其大小由数量决定虚方法乘以虚拟类的数量,并且与运行时的实例数无关

静态和动态链接的代码都是如此。实际代码都存在于“文本”区域。大多数操作系统实际上可以跨多个应用程序共享dll代码,因此如果多个应用程序使用相同的dll,则只有一个副本驻留在内存中,并且两个应用程序都可以使用它。如果只有一个应用程序使用链接代码,显然共享内存不会有额外的节省。

答案 3 :(得分:1)

您无法完全准确地说出一个类或X对象占用RAM的内存量。

然而,为了回答你的问题,你认为代码只存在于一个地方是正确的,它永远不会被“分配”。因此,代码是每个类,无论您是否创建对象,都存在。代码的大小由编译器决定,即使编译器经常被告知优化代码大小,也会导致不同的结果。

虚拟函数没有什么不同,除了(小)增加的虚拟方法表开销,通常是每个类。

关于DLL和其他库......根据代码的来源,规则没有区别,因此这不是内存使用的一个因素。

答案 4 :(得分:1)

如果编译为32位。那么sizeof(Bar)应该产生4。 Foo应该添加10个字节(2个字节+ 2个字符)。

因为Foo是从Bar继承的。这至少是4 + 10字节= 14字节。

GCC具有打包结构的属性,因此没有填充。在这种情况下,100个条目将占用1400个字节+一个微小的开销,用于协调分配+一些内存管理的开销。

如果未指定packed属性,则取决于编译器的对齐方式。

但是这并没有考虑vtable占用多少内存和编译代码的大小。

答案 5 :(得分:1)

上面给出的信息非常有用,并且让我对C ++内存结构有了一些了解。但我想补充的是,无论一个类中有多少个虚函数,每个类总会只有1个VPTR和1个VTABLE。在所有VPTR指向VTABLE之后,在多个虚拟功能的情况下不需要多个VPTR。

答案 6 :(得分:0)

您的估算在您提交的基本情况下是准确的。每个对象还有一个带有每个虚函数指针的vtable,因此期望每个虚函数有一个额外指针的内存值。

来自任何基类的成员变量(和虚函数)也是该类的一部分,因此请包含它们。

就像在c中一样,您可以使用sizeof(classname / datatype)运算符来获取类的字节大小。

答案 7 :(得分:0)

是的,没错,在创建对象实例时,代码不会重复。就虚函数而言,使用vtable确定正确的函数调用,但这不会影响对象的创建。

DLL(通常是共享/动态库)被内存映射到进程的内存空间。每次修改都以Copy-On-Write(COW)的形式进行:一个DLL只加载一次到内存中,每次写入一个可变空间,就会创建该空间的副本(通常是页面大小)。

答案 8 :(得分:0)

很难对你的问题给出一个确切的答案,因为这是依赖于实现的,但是32位实现的近似值可能是:

int Bar::mProtectedVar;    // 4 bytes
int Foo::mPublicVar;        // 4 bytes
char Foo::mPublicVar2;     // 1 byte

此处存在所有问题,最终总数可能为12个字节。您还将拥有一个vptr - 比如说4个字节。因此,每个实例的数据总大小约为16个字节。我们无法确定代码将占用多少空间,但您认为所有实例之间只共享一个代码副本是正确的。

当你问

  

我认为dll有自己的堆栈,   堆,代码和数据段。

答案是,DLL中的数据与应用程序中的数据之间确实没有太大区别 - 基本上它们在它们之间共享所有内容,当你考虑它时必须如此 - 如果它们有不同的堆栈(例如,函数调用如何工作?