В C/C++ связанный список имеет только указатель головы, выделенный в стеке, и другие узлы, выделенные в куче. Это может вызвать утечку памяти?

Для C и C++ связанный список с указателем, указывающим на его головной узел. Но все узлы выделяются в куче с помощью malloc() или new. Когда указатель головы выходит за пределы своей области, например. его функция завершается, все узлы, выделенные в куче, будут потеряны. Правильно? Это утечка памяти?

Как C/C++ справляется с такой проблемой? Он автоматически вызывает делокатор? (например, бесплатно() или удалить)?


person Jack    schedule 19.11.2011    source источник
comment
Да, это утечка памяти, если только ваши указатели не используют shared_ptr или что-то подобное.   -  person Mordachai    schedule 20.11.2011
comment
Если какая-либо память явно выделяется с помощью new или malloc, ее необходимо явно уничтожить с помощью удаления или free(). Он НЕ очищается возвратом функции.   -  person Ahmed Masud    schedule 20.11.2011
comment
Как C/C++ справляется с такой проблемой? программист отвечает за управление памятью в C/C++   -  person A. K.    schedule 20.11.2011
comment
Но современный способ нести ответственность — использовать shared_ptr или что-то подобное, или использовать семантику значений и стандартные контейнеры (или использовать стандартные контейнеры с shared_ptr и т. д.).   -  person Mordachai    schedule 20.11.2011
comment
@Mordachai: Честно говоря, если вы на самом деле реализуете такую ​​​​фундаментальную структуру данных, вы бы не делегировали ответственность еще одному фундаментальному DS. Вместо этого вам просто нужно немного подумать об этом и правильно запрограммировать. Не то, чтобы это тривиально, но это тот уровень, на котором вам приходится иметь дело со временем жизни вручную. (Хотя, возможно, мы можем говорить об использовании unique_ptrs узлов.)   -  person Kerrek SB    schedule 20.11.2011
comment
Выберите язык, пожалуйста. Ответ для C++ сильно отличается от ответа для C.   -  person Robᵩ    schedule 20.11.2011
comment
На самом деле, использование unique_ptr или подобного в реализации связанного списка, как правило, очень плохая идея, потому что, когда вы уничтожаете голову, деструкторы каждого узла, в свою очередь, неизбежно в конечном итоге вызовут друг друга рекурсивно. Таким образом, размер вашего контейнера фактически ограничен размером вашего стека, хотя вы не узнаете об этом до времени очистки, когда ваш процесс взорвется. Это случай, когда вы хотите управлять им с уникальным правом собственности и использовать циклическую рекурсию для удаления.   -  person Steve Jessop    schedule 20.11.2011
comment
(Вы могли бы добиться этого, сбросив интеллектуальные указатели в разумном порядке, но если вам все равно придется разбирать список вручную, вы на самом деле не получаете преимущества интеллектуального указателя, так что это примерно такая же плохая ситуация, как если бы вы пришлось удалять узлы вручную).   -  person Steve Jessop    schedule 20.11.2011


Ответы (3)


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

std::vector‹>
std::list‹>

Но чтобы выбрать контейнер, важно знать, что вы делаете, каково должно быть время жизни.

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

Вы должны изменить свой подход к проблеме, чтобы воспринимать проблему как «объекты, выделенные вручную». Как только вы совершите этот скачок и поймете, что это плохой путь, вы сможете выбирать между «они являются объектами-значениями» или «они должны управляться с помощью shared_ptr».

ПРИМЕР 1: использование shared_ptr для хранения новых объектов (это подход, который следует использовать, если копирование вокруг MyNode — плохая идея [производительность, собственные ресурсы, сохраненное состояние]):

void MyFunction()
{
  typedef boost::shared_ptr<MyNode> NodePtr;
  std::list<NodePtr> my_list;
  my_list.push_back(NodePtr(new MyNode(args...)));
  my_list.push_back(NodePtr(new MyNode(args...)));
  ...
  // when this function exits, the nodes, which are owned by shared_ptr's
  // which are themselves owned by a stack instance of std::list<> 
  // will be automatically deleted, no leaks anywhere...
}

EX2: Это то, что вы делаете, если ваши узлы дешевы, их можно рассматривать как копируемые объекты (семантика значений):

void MyFunction()
{
  std::vector<MyNode> my_list;
  my_list.push_back(MyNode(args...));
  my_list.push_back(MyNode(args...));
  ...
  // when this function exits, the nodes, which are shored directly as copies
  // in the vector container, will be automatically deleted, no leaks anywhere...
}

И если вы действительно по какой-то причине предпочитаете управлять экземплярами вручную (обычно вы делаете это, потому что время жизни на самом деле не привязано к времени жизни одного контейнера, а имеет некоторый нерегулярный жизненный цикл, который не может быть аккуратно инкапсулирован ничем, кроме пользовательского алгоритма):

void MyFunction()
{
  std::list<MyNode*> my_list;
  my_list.push_back(new MyNode(args...));
  my_list.push_back(new MyNode(args...));
  ...
  // we must manually deallocate the nodes because nothing else has been given
  // responsibility anywhere (we're manually managing them)
  typedef std::list<MyNode*>::iterator iterator;
  for (iterator it = std::begin(my_list), end = std::end(my_list); it != end; ++it)
    delete *it;  // manually releases our allocated memory from the heap
  // the "head" is still deleted automatically because it is stack allocated
  // (the list object)
}
person Mordachai    schedule 19.11.2011
comment
Я не думаю, что стандартный контейнер может автоматически обрабатывать утечку памяти. Например, std::list‹int*› myList. myList.push_back(ptr1), myList.push_back(ptr2). После того, как функция myList существует, вся память, на которую указывают ptr1 и ptr2, теряется. Это утечка памяти. Если мы изменим int* на shared_ptr‹int›, эта проблема может быть исправлена? - person Jack; 20.11.2011
comment
Или что, если shared_ptr‹int›myPtr = &myList.front() ? это может решить проблему. - person Jack; 20.11.2011
comment
Нет, если вы вставите необработанные указатели, нет. Но это означает, что я буду вручную управлять временем жизни ваших содержащихся объектов. Если вы хотите, чтобы библиотека обрабатывала это, либо используйте объекты-значения (копируемые), либо используйте shared_ptr, чтобы встроить их в контейнер и не беспокоиться о времени жизни. - person Mordachai; 20.11.2011
comment
@ Джек, вы не можете просто назначить заголовок своего связанного списка стандартному списку и ожидать, что это будет иметь какое-либо значение. Я добавлю решение, управляемое вручную, к вышеизложенному, если это поможет вам... - person Mordachai; 20.11.2011

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

person Paul Manta    schedule 19.11.2011
comment
Но это ужасная вещь, на которую можно положиться. Это не звуковое программирование (но об этом полезно знать). - person Mordachai; 20.11.2011
comment
@Mordachai: хотя, вы знаете, когда вы закрываете программу, к которой вы не прикасались какое-то время, и она пыхтит, прежде чем окончательно исчезнуть? Значительная часть этого времени связана с тем, что звуковое программирование диктует, что он должен перебирать всю выделенную ему память, вызывая ее из дискового кеша, чтобы освободить ее. Иногда для больших приложений хорошим вариантом является просто выйти из процесса в режиме выпуска, но выполнить освобождение в режиме отладки, чтобы средство проверки утечек могло найти подлинные ошибки. C++ делает это довольно сложным, конечно, это более осуществимо в приложениях C. - person Steve Jessop; 20.11.2011
comment
@SteveJessop Есть способы справиться с этим, не просто отказываясь от объектов. И я был бы согласен с очень тщательным выбором среди тех объектов, от которых вы решили отказаться таким образом, поскольку вы должны убедиться, что такие объекты сами по себе не содержат ресурсы за пределами ОЗУ. Это, безусловно, продвинутая тема промышленного проектирования программного обеспечения, а не то, что нужно разбрасывать для Как мне... базовый разговор/вопрос на С++ :) - person Mordachai; 20.11.2011
comment
@Mordachai Конечно, полагаться на очистку ОС после вас неприемлемо, но ОП спросил, что происходит с утечкой памяти. :) - person Paul Manta; 20.11.2011

Да, это утечка памяти. Нет, ни C, ни C++ (такого языка, как C/C++, нет) не имеют никаких механизмов для этого. Это не JAVA или более поздний язык. Это зависит от ВАС, как кодера.

person Gangnus    schedule 19.11.2011
comment
C++ имеет способ очистки, иначе задокументированное поведение std::forward_list было бы ложью - person Caleth; 22.06.2021