Освобождение памяти из map. C

#c #stl #memory-leaks #map

#c #stl #утечки памяти #словарь

Вопрос:

 std::map<int, int> * mp = new std::map<int, int>;
for(int i = 0; i < 999999; i  ){
  mp->insert(std::pair<int, int>(i, 999999-i )); 
}
p("created");
//mp->clear();     -     doesn't help either
delete mp;
p("freed");
  

Проблема в том, что «удалить mp» ничего не делает. Для сравнения:

 std::vector<int> * vc = new std::vector<int>;
for(int i = 0; i < 9999999; i  ){
  vc->push_back(i); 
}
p("created");
delete vc;
p("freed");
  

освобождает память. Как освободить память из map?
PS: p(«строка») просто приостанавливает программу и ожидает ввода.

Комментарии:

1. Откуда вы знаете, что «удаление mp ничего не делает»? И почему вы создаете новую карту?

2. Откуда вы знаете, что «удаление mp ничего не делает»? — Я просто смотрю на диспетчер задач («top» в Linux) и вижу оперативную память, используемую приложением. И почему вы создаете новую карту? — просто экспериментирую.

3. почему вы хотите сделать именно так?

4. Я бы не назвал «проверку оперативной памяти, используемой приложением» надежным тестом. Если вы используете Linux, используйте valgrind вместо этого.

Ответ №1:

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

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

Вы знаете, что память освобождена, потому что так гласит семантика языка.

Комментарии:

1. ну, если я повторю 99999999, программа съест до 15% оперативной памяти моего компьютера (4 ГБ), поэтому я не говорю о кбайтах оперативной памяти. если я использую vector, после удаления 15% использование оперативной памяти упадет до 0.0%. Если я использую map, я не вижу никакой разницы после «удаления».

2. @foret: вероятно, это потому, что vector<T> должен использовать одно непрерывное выделение, тогда как std::map<T,U> будет использовать много небольших выделений (по одному на узел ключ / значение). Обычно выделения > 4 КБ передаются операционной системе напрямую.

3. @foret: Чтобы убедиться в этом на практике, просто поместите код в цикл и запустите его, например, десять раз. В вашем случае после первой итерации цикла не должно происходить дальнейшего увеличения объема памяти.

Ответ №2:

На самом деле, если следующий код не протекает:

 {
  std::map<int, int> some_map;
}
  

Следующий код также не должен протекать:

 {
  std::map<int, int>* some_map = new std::map<int, int>();
  /* some instructions that do not throw or exit the function */
  delete some_map;
}
  

Это применимо к любому типу, с которым вы используете new , при условии, что тип хорошо написан. И std::map , вероятно, написан очень хорошо.

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

Комментарии:

1. Более вероятно, что схема выделения STL сохраняет освобожденную память для последующего выделения 🙂

Ответ №3:

Как упоминает Дэниел, оперативная память, используемая приложением, не обязательно является показателем утечки памяти. Что касается поведения, которое вы заметили в отношении векторов, вектор гарантирует, что расположение памяти является непрерывным, и, следовательно, при создании вектора размером 99999999 все элементы 99999999 расположены последовательно и будут составлять довольно значительный кусок памяти. Удаление этого определенно повлияет на размер процесса. Карта ведет себя по-разному (согласно Википедии, ее часто реализуют как самобалансирующееся двоичное дерево), поэтому я предполагаю, что ее удаление вызывает фрагментацию в пространстве памяти процесса и, возможно, из-за этого память процесса не удаляется немедленно. Лучшим способом обнаружения утечек памяти было бы использовать такой инструмент, как valgrind, который четко укажет, что не так.

Комментарии:

1. Хотя википедия является хорошей отправной точкой для исследований, она не самое подходящее место для цитирования в качестве ссылки (г-на Уэльса несколько раз цитировали по этому вопросу). Однако это хорошее место для того, чтобы привести вас к авторитетным ресурсам (в данном случае к требованиям контейнеров c STL), которые должны показать вам, что нет необходимости реализовывать map в виде сбалансированного дерева) (хотя вы правы в том, что большинство реализаций используют этот метод.

2. @Martin — Согласен, спасибо, что указали на это. Обязательно будем иметь это в виду.

Ответ №4:

Чтобы точно определить, освобождаете вы память или нет, попробуйте добавить наблюдаемые эффекты к деструкторам вашего объекта и … понаблюдайте за ними. Например, вместо map создайте пользовательский класс, который выдает выходные данные при вызове деструктора. Что-то вроде этого:

 #include <map>
#include <iostream>
#include <utility>

class Dtor{
        int counter;
    public:
        explicit Dtor(int c):counter(c) {std::cout << "Constructing counter: " << counter << std::endl; }
        Dtor(const Dtoramp; d):counter(d.counter) {std::cout << "Copy Constructing counter: " << counter << std::endl; }
        ~Dtor(){ std::cout << "Destroying counter: " << counter << std::endl; }
};

int main(){
    std::map<int, const Dtoramp;> * mp = new std::map<int, const Dtoramp;>;
    for (int i = 0; i < 10;   i){
        mp -> insert(std::make_pair(i, Dtor(i)));
    }
   delete mp;
   return 0;
}
  

Вы увидите, что удаление указателя вызывает деструктор ваших объектов, как и ожидалось.

Комментарии:

1. Если вы делаете это, часто бывает полезно включить оператор присваивания (поскольку он будет сгенерирован в любом случае)

2. @Martin: да, это был просто быстро записанный пример. IIRC Я добавил конструктор копирования просто для визуального сопоставления деструкторов.