C ++ управление памятью и векторы - PullRequest
5 голосов
/ 08 июня 2009

Я очень запутался в управлении памятью по отношению к векторам и мог бы объяснить некоторые базовые понятия.

У меня есть программа, которая использует большие векторы. Я создал векторы с помощью оператора new и выпустил их в конце программы с помощью delete , чтобы вернуть память.

У меня такой вопрос: если по какой-либо причине происходит сбой или прерывание программы, строки delete будут пропущены, есть ли способ восстановить память даже в этом сценарии? .

У меня также есть несколько других больших векторов, которые я назначаю без ключевого слова new . Я читал, что они будут создаваться в куче, но в любом случае их не нужно освобождать, поскольку управление памятью происходит «под капотом». Однако я не уверен, что это так, поскольку каждый раз, когда я запускаю свою программу, я теряю ОЗУ.

Итак, мой второй вопрос: могут ли векторы, созданные без ключевого слова new , действительно быть оставлены на их собственные устройства и доверяться для очистки после себя, даже если код прерван в середине потока.

И я предполагаю, что третий вопрос, который только что возник, - если векторы автоматически создаются в куче, зачем вам использовать с ними ключевое слово new ? Спасибо за прочтение, бен

Ответы [ 8 ]

15 голосов
/ 08 июня 2009

Я подозреваю, что ваши вопросы касаются std :: vector (в отличие от массива T []).

  1. Когда по какой-либо причине происходит сбой или прерывание работы вашего приложения, ОС освобождает память. Если нет, то вы используете действительно редкую ОС и обнаружили ошибку.
  2. Необходимо различать память, используемую самим вектором, и память, содержащуюся в нем. Вектор может быть создан в куче или в стеке, как вы заметили, память, которую он выделяет для содержащихся в нем элементов, всегда находится в куче (если вы не предоставите свой собственный распределитель, который делает что-то еще). Память, выделенная вектором, управляется реализацией вектора, и если вектор разрушается (либо потому, что он выходит за пределы области действия для вектора в стеке, либо потому, что вы удаляете вектор в куче), его деструктор обеспечивает память освобождена.
8 голосов
/ 08 июня 2009

Не используйте 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;
}
4 голосов
/ 08 июня 2009

Да, вы можете доверять векторам, чтобы убирать за собой.

ОДНАКО Вы не можете доверять тому, что вектор вещей держит для очистки после себя. То, что нужно очистить, может быть чем-то, что сохраняется за пределами вашего приложения. Если его память, это не беспокойство. Если он удостоверится, что все теги XML закрыты, то ОС не сможет вам помочь.

Например, что, если у вас есть вектор какого-нибудь объекта Wankky Lock, например:

  class CLock
  {
  public:
      CLock() {}
      ~CLock() {}

      void Lock(...) {...}

      void Unlock(...) {...}
  };

  std::vector<CLock> myLockVec;

Как ваш вектор CLock's узнает, что разблокировать все, когда это будет сделано? Вектор не создан, чтобы знать о замках.

По сути, это та же самая ситуация, что и наличие вектора указателей:

 std::vector<int*> myIntVec;

Как вектор знает, какие указатели здесь были удалены и имеют значение NULL, а какие на самом деле есть? Возможно, некоторые из них были удалены и установлены в ваше специальное значение 0xdeadbeef, что означает удаленный.

Дело в том, что вектор не имеет возможности узнать это или знать, что его элементы являются указателями или замками или чем-то еще. Они просто должны быть объектами, которые имеют конструкторы по умолчанию и являются копируемыми, и удовлетворяют другим таким требованиям, которые вектор предъявляет к своим элементам.

Решение состоит в том, чтобы быть уверенным, что любой вектор HOLDS должен отвечать за его очистку. Это называется RAII - Распределение ресурсов - Инициализация, более важно здесь, Уничтожение ресурсов - это Распределение. В приведенном выше примере с нашим CLock ответ очевиден, обязательно откройте его, когда закончите!

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

Но с указателями это не так очевидно. Решение - заключить указатель в класс smart_ptr. Самыми плодовитыми из них являются бустерское семейство умных понитеров .

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

      ~CSmartPointer()
      {
         delete m_ptr;
      }
}

Дополнительные функции включены в игру с указателями, такими как подсчет ссылок, но приведенный выше пример должен дать вам представление о природе проблемы и о том, как она обычно решается.

3 голосов
/ 08 июня 2009

Полагаю, вы говорите о std :: vector, а не о языковых массивах.

  1. При сбое программы ОС восстанавливает память
  2. std :: vector освобождает память, которую он выделяет. Если вы храните указатели, они не будут удалены.
  3. Векторы создаются как любая другая переменная, их нет в куче только потому, что они являются векторами.
3 голосов
/ 08 июня 2009

Любая память, созданная вашей программой, будет освобождена при выходе. Это особенность операционной системы, не имеющая ничего общего с используемым языком программирования.

«каждый раз, когда я запускаю свою программу, я теряю ОЗУ», должно быть из-за какого-то другого эффекта - как вы это измеряете?

Относительно того, почему вы используете «новый» - две причины:

  • Вы хотите контролировать, когда они будут освобождены
  • Вы хотите, чтобы они сохранялись после выхода из текущей функции.
2 голосов
/ 08 июня 2009

Один из нас двоих здесь немного смущен.

Если вы используете std :: vector, вам не нужно вручную выделять память для его элементов. Дополнительное пространство будет автоматически выделяться при необходимости всякий раз, когда вы выполняете push_back (). Если вам по какой-то причине нужно предварительно выделить все пространство, вы можете вызвать функцию Reserve (). В любом случае, при разрушении вектора память освобождается автоматически.

Если вы делаете новый std :: vector, вы получаете указатель на вектор. Это ничем не отличается от вызова нового в любом другом классе. Вы создаете указатель на объект этого класса, и он будет разрушен при вызове delete. Если вам не нравится это поведение, попробуйте создать свой вектор в стеке.

1 голос
/ 08 июня 2009

Другой сценарий, не упомянутый в отношении того, когда вы хотите использовать «new», - это в некоторых случаях, когда вектор является переменной-членом класса. NULL может использоваться как дополнительный семафор, например, во время создания по требованию; Кроме того, если использование вектора редко заполняется в вашем классе, то даже его создание, если это действительно не нужно, сэкономит вашу память за счет дополнительного 4-байтового штрафа во всех экземплярах, а также штрафа во время выполнения за счет косвенного обращения указателя.

1 голос
/ 08 июня 2009

Что касается "потерянной памяти", что говорит @RichieHindie.

По второму вопросу:

могут ли векторы, созданные без ключевого слова NEW, действительно быть оставлены на их усмотрение? собственные устройства и доверяют, чтобы убрать за собой, даже если код прерывается средний поток

В то время как обычное завершение программы (включая завершение за исключением) обеспечивает выполнение деструкторов (с некоторыми придирками к статическим данным - теоретически они должны выполняться тоже, на практике иногда могут возникать проблемы), достаточно серьезный сбой процесс не может гарантировать никакого поведения - например, kill -9 гарантированно завершит вашу программу как можно скорее, не давая ему возможности выполнить какие-либо деструкторы или что-либо еще.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...