【问题标题】:C++ memory management and vectorsC++ 内存管理和向量
【发布时间】:2010-11-01 05:05:53
【问题描述】:

我对与向量相关的内存管理感到非常困惑,并且可以解释一些基本概念。

我有一个使用大向量的程序。 我使用 new 运算符创建了向量,并在程序结束时使用 delete 释放它们以获得记忆回来了。

我的问题是,如果程序由于某种原因崩溃或中止,delete 行将被遗漏,有没有办法恢复内存,即使在这个场景。

我还分配了一些其他大型向量,但没有使用 new 关键字。 我已经读过这些将在堆上创建,但无论如何都不需要释放,因为内存管理是在“幕后”处理的。 但是我不确定情况是否如此,因为每次运行我的程序都会丢失 RAM。

所以我的第二个问题是,没有 new 关键字创建的向量是否真的可以留给他们自己的设备,并且即使代码在中间中止,也可以信任他们自己清理流。

我想第三个刚刚浮现在脑海中的问题是,如果向量是在堆上自动创建的,你为什么要对它们使用 new 关键字? 谢谢阅读, 本

【问题讨论】:

  • “每次我运行我的程序都会丢失 RAM”是指“我的可用 RAM 会变小,直到我退出程序”还是“即使我退出程序,我的可用 RAM 也会变小,并且下次我运行时它会变得更小,直到有一天我完全没有内存了”?
  • 我附议 Max 的问题。我相信 Windows 实际上不会卸载已终止的程序,除非它需要。这样他们在第一次之后就可以更快地启动。
  • “我想我想到的第三个问题是,如果向量是在堆上自动创建的,你为什么要对它们使用 new 关键字?”如果您需要将向量传递到当前范围之外的点,您只需要这样做。这在实践中比较少见。

标签: c++ memory vector


【解决方案1】:

不要使用new 创建向量。只需将它们放在堆栈上即可。

向量的析构函数自动调用向量中每个元素的析构函数。所以你不必担心自己删除对象。但是,如果您有一个指针向量,则指针所指的对象将不会 被清除。这是一些示例代码。为了清楚起见,我省略了大部分细节:

class HeapInt
{
    public:
        HeapInt(int i) {ptr = new int(i);}
        ~HeapInt() {delete ptr;}
        int& get() {return *ptr;}
    private:
        int* ptr;
};

int main()
{
    // this code DOES NOT leak memory
    std::vector<HeapInt> vec;
    for (int i = 0; i < 10; ++i)
    {
       HeapInt h(i);
       vec.push_back(h);
    }
    return 0;
}

即使 main() 抛出异常,也不会丢失内存。然而,这段代码确实泄漏了内存:

int main()
{
    // this code though, DOES leak memory
    std::vector<int*> vec;
    for (int i = 0; i < 10; ++i)
    {
       int* ptr = new int(i);
       vec.push_back(ptr);
    }
    // memory leak: we manually invoked new but did not manually invoke delete
    return 0;
}

【讨论】:

  • 是的,但是问这个问题的人显然不知道向量通常应该放在堆栈上。
  • 第二个示例如何泄漏内存?程序结束,操作系统回收内存,或者我错过了一些关于内存管理的非常重要的东西
  • 嗯,是的,当任何程序结束时,操作系统会回收内存。但如果这是程序多次调用的函数,那就是内存泄漏。
【解决方案2】:

关于何时要使用“new”的另一种未提及的情况是,在某些情况下,向量是类的成员变量。 NULL 可以用作附加信号量,例如在按需创建期间;此外,如果向量使用在您的类上稀疏填充,那么除非确实需要它,否则甚至不创建向量将节省您的内存,但代价是所有实例的额外 4 字节损失以及指针间接的运行时损失。

【讨论】:

    【解决方案3】:

    我们两个中的一个在这里有点困惑。

    如果您使用 std::vector,则无需手动为其元素分配内存。每当您执行 push_back() 时,将在需要时自动分配额外空间。如果您出于某种原因需要预先分配所有空间,您可以调用reserve()。无论哪种方式,当向量被破坏时,内存都会自动为您释放。

    如果你在做新的 std::vector,你会得到一个指向向量的指针。这与在任何其他类上调用 new 没有什么不同。您创建一个指向该类对象的指针,当您调用 delete 时,它​​将被破坏。如果您不喜欢这种行为,请尝试在堆栈上创建向量。

    【讨论】:

      【解决方案4】:

      是的,您可以相信向量会自行清理。

      但是你不能相信这个东西向量会自己清理。需要清理的可能是在您的应用程序之外持续存在的东西。如果它的记忆,这不是一个担心。如果它确保 XML 标记全部关闭,那么操作系统将无法为您提供帮助。

      例如,如果你有一个像这样的一些不稳定的锁对象的向量:

        class CLock
        {
        public:
            CLock() {}
            ~CLock() {}
      
            void Lock(...) {...}
      
            void Unlock(...) {...}
        };
      
        std::vector<CLock> myLockVec;
      

      您的 CLock 向量如何知道在完成后解锁所有内容? Vector 不是为了解锁而构建的。

      这与拥有一个指针向量的情况基本相同:

       std::vector<int*> myIntVec;
      

      向量如何知道此处的哪些指针已被删除并为 NULL,以及哪些指针确实存在?也许有些已经被删除并设置为你的特殊值 0xdeadbeef,意思是删除。

      关键是向量无法知道这一点或知道它的元素是指针或锁或其他任何东西。它们只需要具有默认构造函数且可复制,并满足 vector 对其元素的其他此类要求。

      解决方案是确保任何向量 HOLDS 都需要负责其清理。这叫做RAII——资源分配就是初始化,这里更重要的是,资源销毁就是释放。通过上面我们的 CLock 示例,答案显而易见,完成后一定要解锁!

       class CLock
       {  
            ...
            ~Clock()
            {
                if (locked)
                {
                    Unlock();
                }
            }
       } 
      

      但是有了指针就不是那么明显了。解决方案是将指针包装在 smart_ptr 类中。其中最多产的是boost family of smart poniters

      class CSmartPointer<T>
      {
            CSmartPointer( T* rawPtr)
            {
               m_ptr = rawPtr;
            }
      
            ~CSmartPointer()
            {
               delete m_ptr;
            }
      }
      

      其他功能通过引用计数等指针发挥作用,但上面的示例应该让您了解问题的本质及其通常如何解决的要点。

      【讨论】:

        【解决方案5】:

        我怀疑你的问题是关于 std::vector(而不是数组 T[])。

        1. 当您的应用程序因任何原因崩溃或中止时,操作系统会回收内存。如果不是,您使用的是真正罕见的操作系统并发现了一个错误。
        2. 您需要区分向量本身使用的内存和它包含的对象的内存。如您所述,向量可以在堆上或堆栈上创建,它为其包含的元素分配的内存始终在堆上(除非您提供自己的分配器来执行其他操作)。向量分配的内存由向量的实现管理,如果向量被销毁(因为它超出了堆栈上向量的范围,或者因为您删除了堆上的向量),它的析构函数确保所有内存已释放。

        【讨论】:

        • 大多数嵌入式操作系统和许多 RTOS 不会在进程崩溃时清理资源。
        【解决方案6】:

        @RichieHindie 所说的“失去的记忆”。

        第二个问题:

        没有NEW关键字创建的向量真的可以留给他们的吗? 拥有自己的设备并受信任即使有代码也会自行清理 在流程中被中止

        虽然正常的程序终止(包括异常终止)可确保析构函数执行(对于静态数据的析构函数存在一些小问题——理论上它们也应该运行,但实际上您可能偶尔会遇到问题),但足够硬的进程不能保证任何行为——例如,kill -9保证尽快终止您的程序,而不给它执行任何析构函数或其他任何东西的机会。

        【讨论】:

        • 未处理的异常(导致运行时最终调用 terminate() 的异常)可能不会导致析构函数执行。在这种情况下,堆栈展开行为是实现定义的。
        【解决方案7】:

        我想你说的是 std::vector 而不是语言数组。

        1. 当程序崩溃时,操作系统会恢复其内存
        2. std::vector 释放它分配的内存。如果您正在存储指针,它们将不会被删除。
        3. 向量被创建为任何其他变量,它们不在堆中只是因为它们是向量。

        【讨论】:

          【解决方案8】:

          您的程序创建的任何内存都将在退出时被释放。这是操作系统的一项功能,与您使用的编程语言无关。

          “每次我运行我的程序时,我都会丢失 RAM”一定是由于其他一些影响 - 你是如何测量的?

          至于你为什么要使用“新” - 两个原因:

          • 您想控制它们何时被释放
          • 您希望它们在当前函数退出后仍然存在。

          【讨论】:

          • 与给 Tobias 的评论相同。大多数嵌入式操作系统和许多 RTOS 不会在进程崩溃时清理资源。
          • +1 指出许多人没有意识到的。虚拟内存管理器是一种奢侈品,通常被认为是理所当然的。
          猜你喜欢
          • 1970-01-01
          • 2012-06-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-10
          • 2021-11-01
          • 2013-05-09
          相关资源
          最近更新 更多