【问题标题】:Freeing memory allocated to the heap stored inside a vector释放分配给存储在向量内的堆的内存
【发布时间】:2014-10-15 18:08:25
【问题描述】:

我通过将其内存分配给堆来创建了一个向量。然后我创建了 10 个也分配给堆内存的字符串对象,并将它们存储在向量中。我尝试使用delete 运算符释放与每个新字符串对象关联的内存,但我不知道该怎么做。我正在使用 C++ 11。

#include <vector>
#include <string>
#include <iostream>

using namespace std;

int main()
{
   vector<string> *v = new vector<string>;

   for(int i = 0; i < 10; i++) {
     // allocate a new string object on the heap
     string *a = new string("Hello World");
     //de-reference the string object
     v->push_back(*a);
   }

  // show the contents of the vector
  for(auto i = v->begin(); i != v->end(); ++i) {
    // okay so this makes a lot more sense than:
    // const string &s = *i;
    // this way we create a pointer to a string object
    // it is a lot more clear this way
    const string *s = &(*i);
    cout << *s << " " << s->length() << endl;
  }

  cout << endl << endl;

  for(vector<string>::iterator it = v->begin(); it != v->end(); ++it) {
    delete &it;
    v->erase(it);
  }

  for(auto i = v->begin(); i != v->end(); ++i) {
   cout << *i << endl;
  }
  cout << endl << "Size: " << v->size() << endl;

  delete v;
}

g++ -std=c++11 main.cc -o main

我的错误是并非所有对象都被删除。在最后 4 个语句之后,我最终得到了 5 个对象。一旦这些操作完成,我希望向量内有零个对象。

我的输出:

Hello World 11
Hello World 11
Hello World 11
Hello World 11
Hello World 11
Hello World 11
Hello World 11
Hello World 11
Hello World 11
Hello World 11


Hello World
Hello World
Hello World
Hello World
Hello World
Size: 5

问题在于并非所有对象都被删除。

【问题讨论】:

  • 为什么要动态分配向量?没有理由这样做。您根本不需要执行任何手动内存管理。
  • 这是出于教育目的。除了学习,我所做的一切都是徒劳的。
  • 只需删除所有指针和所有新闻,它就会按照您想要的方式工作。
  • 第一个 for 循环是内存泄漏的天堂。更不用说所有这些动态分配容易出错且无用。容器正是为您处理这个问题。 *new X(); 是“内存泄漏运算符”顺便说一句,这就是你在 for 循环中所做的事情。
  • @self - This is for educational purposes. 这并不难——你用new/new[] 分配的东西你用delete/delete[] 释放。你真正学到的(如果你可以称之为学习的话)就是如何编写一系列代码,以便释放分配的内容。

标签: c++ memory-management vector


【解决方案1】:

我认为您在无数问题中担心的特定问题是您的 for 循环没有删除所有项目,即:

for(vector<string>::iterator it = v->begin(); it != v->end(); ++it) {
  delete &it;
  v->erase(it);
}

您的问题是您正在更改您正在迭代的向量,这会导致未定义的行为,因为您没有删除所有值。 (向量的迭代器只有在您不添加或删除值时才有效)。

可以解释为什么它只做 5,但答案不会是跨平台的。在这种情况下,编译器可以随意做任何事情。编译器让恶魔飞出你的鼻子同样有效。

基本上,您正在删除一个值,然后它们会移动到下一个索引。因此,您擦除 0 处的内容,这会将 1 处的内容拉到 0 处的内容。然后您移动到索引 1,其中包含曾经在索引 2 中的内容。然后您将其删除。本质上,您从向量中删除了所有偶数索引。

编辑:将问题减少到最小的可重现性:

std::vector<int> vals;

for (int i = 0; i < 11; i++0) vals.push_back(i);

for (std::vector<int>::iterator i = vals.begin(); i != vals.end(); ++i)
{
   vals.erase(i);//after this point, i's behavior is undefined!
}

编辑 3:枚举所有代码问题(基于元对话的建议操作)

  1. Vector 已经在堆中分配了它的集合内存(除了指向内存的指针和大小计数器之外的所有内容)。整个方法是基于不理解这一事实。新向量确实将整个向量放入堆中,这可能是 OP 想要的。
  2. 字符串也在堆中分配它的内存。 new std::string 将简单地分配指向字符数组的指针和堆中的大小。这也是需要注意的地方。
  3. 如果你想要一个指针向量,你应该有std::vector&lt;T*&gt;std::vector&lt;T&gt; 将是 T 实例的向量。
  4. 由于向量的内存已经在堆上分配,这最初看起来是徒劳的。 但是,在某些情况下您可能需要这样的结构。例如,为了避免切片,必须以这种方式存储多态类型。 (不过,我会使用智能指针,但出于教育目的,这是一个很好的练习)。
  5. 在循环 1 中,您动态分配一个字符串。然后,您使用复制构造函数来实例化向量中的实体实例。然后让指针超出范围。这是内存泄漏和低效复制。
  6. 您似乎真的想使用指针作为引用。 std::string&amp; s = *it 比获取地址更具可读性。
  7. juanchopanza 正确地指出您正在删除迭代器的位置,这是更多未定义的行为。 &amp;it 的类型为 std::vector&lt;string&gt;::iterator*。为什么这可能不会崩溃和燃烧是因为迭代器类型仅由内置类型组成,并且在此之后您并没有做太多事情(您可能已经为一些美味的堆栈损坏做好了准备)。如果您有一个 std::vector&lt;T*&gt;(与第 1 项匹配)且该向量拥有唯一所有权,您需要这样做来清理它:

delete &amp;(*it);

但最好是做类似std::vector&lt;std::unique_ptr&lt;T&gt;&gt; 这样的事情而不必担心。

  1. 永远不要使用 std 的全局变量。

【讨论】:

  • @juanchopanza 好点...发生的事情是更多的 NASAL DEAMONS。
  • delete &amp;(*it)delete &amp;it 一样糟糕。他们俩都在破坏由其他人管理的对象。如果你有一个指针向量,那么delete *it 可能是正确的做法。
  • @BenVoigt 哇,好久不见了。我相信这就是为什么我说“在实际的堆代码中”,但我会改变它以使其更清楚。
  • std::vector&lt;T*&gt; 的情况正是你应该使用的情况 delete *it; delete &amp;(*it) 可能在向量实现中有意义,但对于用户代码来说永远不会。
【解决方案2】:

问题在于并非所有对象都被删除。

那是因为你在填充向量时泄漏了字符串。

for(int i = 0; i < 10; i++) {
  string *a = new string("Hello World"); // Leak: who deletes a? Nobody!
  v->push_back(*a);
}

这将是避免特定泄漏的一种方法:

for(int i = 0; i < 10; i++) {
  v->push_back("Hello World");
}

不过,您仍在动态分配v,没有理由这样做。这将是在 C++ 中执行此操作的一种更简单且惯用的方法。与您的版本不同,它不涉及内存泄漏或未定义的行为:

int main()
{
   vector<string> v(10, "Hello World");

   for (auto& s : v)
     cout << s.length() << endl;
}

您不需要指针。您无需致电new。您无需担心内存管理。

【讨论】:

    【解决方案3】:

    当您使用 v->push_back(*a); 时,您将在向量上推送字符串的副本;命令。如果您不想这样做,则需要将向量设为字符串指针向量,然后将指针本身推送到向量上。

    【讨论】:

      【解决方案4】:

      我不明白为什么要动态分配字符串,即使您是按值将字符串存储在向量中。除非您正在学习如何在 C++ 中泄漏内存,否则即使出于教育目的,此 sn-p 也没有意义。

         for(int i = 0; i < 10; i++) {
           // allocate a new string object on the heap
           string *a = new string("Hello World");
           //de-reference the string object
           v->push_back(*a);
         }
      

      如果您想在任何 stl 容器中存储指针,那么我建议使用一些智能指针(不包括 AUTO_PTR),例如 boost::shared_ptr。他们会为您处理内存管理。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-28
        • 2011-03-29
        • 2014-09-05
        • 1970-01-01
        • 2011-05-13
        • 2012-07-27
        • 1970-01-01
        • 2011-12-09
        相关资源
        最近更新 更多