【问题标题】:C++ Memory AllocationC++ 内存分配
【发布时间】:2010-09-15 08:17:53
【问题描述】:

使用 C++ 时, 如果有课:

class MyClass
{
    char memory1bye;
    int memory4bytes;
    int another4bytes;
};

这个类在内存中总共使用了 9 个字节......所以如果我这样做:

MyClass *t1;

这会给我一个可用的类地址,但它会分配 9 个字节吗?它会调用默认构造函数吗? 还是我需要将这 9 个字节分配给类? 如果那时我打电话给:

t1 = (MyClass *)new MyClass;

会被认为是内存泄漏吗?换句话说,旧地址会发生什么?

【问题讨论】:

  • 不要误会,但我认为最好的办法是获得一本关于 C++ 的好书。它不仅应该回答所有这些问题,还应该解释何时使用new,何时不使用new
  • 类的确切大小是sizeof(MyClass)sizeof(*t1)(两者都会给出相同的结果)。为了记账,分配可能需要更多时间。

标签: c++ memory-management


【解决方案1】:
  1. 不要对数据类型的大小做出假设,它们取决于实现。
  2. MyClass *t1 定义了一个未初始化的指针。取消引用它会调用 undefined behavior
  3. t1 = (MyClass *)new MyClass; 在堆上分配内存并创建一个新对象。如果没有使用delete 释放此内存,则会出现内存泄漏。此外,您不需要那里的演员,t1 = new MyClass(); 就足够了。

编辑:关于分配。

MyClass *t1 = NULL; 

声明一个指向MyClass-object 的指针,但它不创建该对象。该指针被初始化为指向0。现在当你这样做时

t1 = new MyClass();

运算符new 创建MyClass 的新实例并将该对象的地址分配给t1。您现在可以通过t1 使用该对象:

t1->doStuff();

您甚至可以创建更多指向同一个对象的指针:

MyClass *t2 = t1;

现在t2t1 指向同一个对象。完成对象后,只需执行以下操作:

delete t1;

delete t2 会产生相同的效果)。现在对象被销毁了,但指针仍然指向同一个地址(这不再安全)。正在做

t2->doStuff();

delete 调用未定义的行为之后。如果我们回到删除之前,考虑一下:

t1 = NULL;
t2 = NULL;

现在我们没有我们创建的对象的地址,所以我们不能在它上面调用delete。这会造成内存泄漏。希望这能让您对正在发生的事情有所了解。现在忘记这一切,阅读RAII

【讨论】:

  • 另外,您可以提及new 中的强制转换不是必需的。
  • 所以基本上将 t1 更改为另一个地址不会被视为内存泄漏,因为 t1 只是指向空白空间的指针?
  • @VirusEcks:new expression 的结果是堆上的一个对象。这些对象将通过在它们上调用运算符delete 来删除,这是通过将它们的地址传递给delete 来完成的。地址存储在指针中。泄漏是如果您丢失了对堆上对象的最后一个引用(指针)。
  • @VirusEcks:我添加了一些解释,希望能帮助您了解发生了什么。
  • @Space_C0wb0y ,.. 谢谢 .. 有道理
【解决方案2】:

仅仅声明一个指针不会分配内存来保存类,也不会调用构造函数。您必须调用 operator new 来实际分配内存并提供对象初始化。此外,不需要强制转换 new 的返回类型。

哦,我也不得不告诉你,在 C++ 中,除了手动管理内存之外,你几乎总是应该寻找其他方法,这些方法是容器类(std::vector、std::deque 等)和智能指针,两者都让管理内存变得不那么痛苦。

【讨论】:

    【解决方案3】:
    1. 正如许多人所说,MyClass 的大小取决于实现。在这种情况下,因为类没有方法,所以你基本上得到了一个结构体,所以可以对大小做出一些合理的猜测。在没有任何异常编译器标志的普通现代 32 位机器上,结构的大小为 12 字节;这是因为在当前架构上,字段默认对齐到 4 字节边界。

      在 64 位机器上它可能更大,但如果它大于 24 字节(即每个字段对齐 8 字节),我会有点惊讶。除非明确告知,否则我认为不会对字段使用大于 8 字节对齐的任何内容,并且对字段使用更大的对齐值没有多大意义,因为内存分配函数本身通常具有 8 字节对齐。 (注意:不要指望这对你的机器来说是真的!)

      真正了解任何事物大小的唯一方法是使用sizeof(MyClass)。您几乎不需要在 C++ 中使用它,因为 new 运算符为您知道这一点并分配所需的空间量。如前所述,请记住,anythingchar 除外)的大小是不可移植的,即使它们实际上并没有无缘无故地变化。

    2. 执行MyClass *t1; 不会分配任何东西。它只是为您提供了一个存储对象地址的位置(特别是MyClass 实例)。默认情况下,如果变量在类或结构定义中的任何局部范围中,则该空间指向 la-la 土地。如果您不打算将地址放入变量中,最好将其显式初始化为NULL,以便它至少指向一个明确的非对象。

    3. 您的t1 = (MyClass *)new MyClass; 包含不必要的强制转换,因为new 无论如何都会返回指向该类型对象的指针。做t1 = new MyClass;就够了。

    4. 如果t1 之前指向一个对象并且是唯一指向它的变量,那么您将发生内存泄漏(假设您没有使用垃圾收集器库;大多数 C++ 程序都是在不使用它们的情况下编写的) .如果有其他东西指向该对象,那么最好由它负责清理它。如果地址没有特别指向任何东西,或者它指向NULL,那么什么都不会泄露。

      请记住,地址不会泄露;对象泄漏。

      您可以通过在堆栈上创建对象(直接使用MyClass t1;)并通过引用而不是地址传递它们来缓解内存泄漏;当对象超出范围时,该对象将被自动删除。当您有一个对象的生命周期不能很好地耦合到特定范围时,这种方法的主要缺点就出现了。那是您使用指针(或智能指针,它隐藏大部分的细节,但会受到一些限制)的时候。真正复杂的代码更适合垃圾收集,尽管它有其自身的权衡(特别是包括更可能增加内存消耗;这是 Java 以比 C++ 更耗内存着称的核心原因)。

    【讨论】:

    • 关于#2,如果结构本身直接在全局级别定义,则结构中的指针默认指向NULL。它是堆栈或堆上的东西,默认情况下是任意的(当然,除非构造函数对此进行了处理)。
    【解决方案4】:
    MyClass* t1;
    

    这实际上并没有为 MyClass 对象分配内存。您的变量只是一个指针,有可能跟踪 MyClass 对象的内存地址,但尚未创建此类对象,并且该指针未设置为指向任何地方。

    (当然,为指针本身分配了一些内存,但如果此语句在函数内部,则在堆栈上,否则为全局。)

    t1 = (MyClass*)new MyClass;
    

    这是创建 MyClass 实例的正确思路。不过,最好的方法通常是在堆栈上进行:

    MyClass t;  // who needs a pointer?
    

    那么您甚至不必考虑内存。缺点是该对象仅在您离开创建它的范围之前存在,如 { 和 } 的嵌套所示。

    如果您希望对象寿命更长,那么您确实需要它在堆上(或者可以使其成为静态/全局变量)。对于堆上的动态分配,只需使用:

    t1 = new MyClass;
    

    您不需要 - 也不希望 - 将返回的指针显式转换为 MyClass*... 如果您在一个地方更改类名而不在另一个地方更改类名,这只是多余的和潜在的错误来源。

    一段时间后,您也会想删除 t1。

    MyClass 的实际大小可能不是 9 字节。它取决于编译器,可能是编译器命令行标志、编译器版本、目标内存模型、操作系统等的函数。

    【讨论】:

    • +1 用于提及堆栈(迁移 Java 程序员通常不知道)。
    【解决方案5】:

    除了其他一些答案:

    1. 类的“大小”受很多因素影响:

      • 成员变量的实现定义大小
      • vtable
      • 成员变量的内存对齐要求
      • 填充

    2. 虽然演员表是不必要的,但它也是糟糕的 C++ 风格。不喜欢使用 C 风格的强制转换,而是使用来自 C++ 的其他更安全(或至少更明确)的强制转换:

      • dynamic_cast<type>()
      • static_cast<type>()
      • reinterpret_cast<type>()

    欲了解更多信息,请参阅C++ Reference Guide - New C++ Cast Operators

    【讨论】:

      【解决方案6】:

      如果你这样做

      MyClass *t1;
      

      您只是在声明一个指向 MyClass 类的指针。你并没有真正分配任何内存。为了创建该类的实例,您可以使用以下任何一种:

      MyClass t2;                  // this calls a default constructor implicitly
      MyClass t3 = MyClass();      // this also calls a default constructor explicitly
      MyClass *t4 = new MyClass;   // calls default constructor implictly
      

      前两个声明使用自动存储,而最后一个声明使用动态存储。如果你为你的类定义了一个参数化的构造函数,那么声明会是这样的:

      MyClass t5(arg1, arg2, arg3);
      MyClass t6 = MyClass(arg1, arg2, arg3);
      MyClass *t7 = new MyClass(arg1, arg2, arg3);
      

      【讨论】:

        【解决方案7】:

        -不保证这个类对象会占用9个字节。这完全是实现特定的行为

        -MyClass *ptr 只是声明了一个指向“MyClass”类型的指针。它还没有指向任何“MyClass”类型的对象。如果它是全局的,它将被零初始化,否则如果这样的指针是本地的(例如函数范围),它将被未初始化。

        -你需要初始化这个指针指向一个'MyClass'对象

        例如假设“m”是“MyClass”类型的对象

        MyClass m; 
        ptr = &m;       // this does not create any new object(no constructor runs)
        

        MyClass *ptr;
        ptr = new MyClass();  // This new expression, allocates memory large 
                              // enough to hold a 'MyClass' object, initializes
                              // the object by running it's constructor
        
        delete ptr;           // delete the MyClass object by running it's 
                              // destructor, return the allocated memory back
                              // to the implementation
        

        -如果你在new之后不删除指针'ptr',那肯定是内存泄漏

        【讨论】:

          【解决方案8】:

          不要假设 MyClass 使用 9 个字节,这取决于机器和编译器!

          我的班级 *t1;

          这会给你一个可用的指针,但是用来存放类的空间还没有分配。所以前两个问题的答案是否定的。

          是的,如果你想使用指针,你必须为自己的类分配空间。当然,您可以通过在堆上创建 MyClass* 来摆脱内存分配:

          MyClass t1();

          当 t1 超出范围时,该内存将自动释放。

          • 我的意思是:堆栈。

          【讨论】:

          • MyClass t1();将在堆栈上,而不是堆上。
          • MyClass t1 (); 不创建任何对象,它是函数原型的声明。此外,“您可以通过在堆上创建 MyClass 来摆脱内存分配”的说法是错误的,我认为您的意思是在堆栈上。
          • “当然你可以通过在堆上创建 MyClass 来摆脱内存分配”。我猜“堆”是指“堆栈”。此外,MyClass t1 (); 没有定义对象。它声明了一个函数。
          • 是的,我的意思是堆栈,感谢您指出,我正在编辑我的答案。
          【解决方案9】:

          类可能不是 9 个字节。如果编译器通过填充结构使其更适合计算机架构,则可能会更多。

          我的班级 *t1;没有给你一个可用的地址。它是一个未初始化的指针,指向一个随机内存位置,并且不知道该地址可能是什么。它不会为您分配任何空间来存储 MyClass 实例,也不会调用构造函数。我建议您在定义指针时对其进行初始化:

          MyClass *t1 = 0;   // use 0, NULL, or null_ptr
          

          你需要为类预留空间并调用构造函数。 'new' 会为您完成这两项工作。

          MyClass *t1 = new MyClass();
          

          别忘了你需要一个“删除”来匹配每一个新的,否则你会写一个内存泄漏。

          【讨论】:

            猜你喜欢
            • 2015-07-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多