#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 Я добавил конструктор копирования просто для визуального сопоставления деструкторов.