Это когда-нибудь нормально *не * использовать free () на выделенной памяти?


Я изучаю Компьютерную инженерию, и у меня есть несколько курсов электроники. Я слышал, от двух моих профессоров (из этих курсов), что можно избежать использования (после malloc(),calloc() и т. д.) потому что выделенные пространства памяти, скорее всего, не будут использоваться снова для выделения другой памяти. То есть, например, если вы выделите 4 байта, а затем отпустите их, у вас будет 4 байта пространства, которые, вероятно, не будут выделены снова: у вас будет дыре.

Я думаю, что это безумие: вы не можете иметь не-игрушка-программы где вы выделяете память в куче, не освобождая ее. Но у меня нет знаний, чтобы объяснить, почему это так важно для каждого malloc() должен быть free().

Итак: существуют ли когда-либо обстоятельства, в которых было бы целесообразно использовать malloc() без использования free()? А если нет, то как я могу объяснить это своим профессорам?

11 83

11 ответов:

легко: просто прочитайте источник в значительной степени любой полусерьезный malloc()/free() реализация. Под этим я подразумеваю фактическое диспетчер памяти это обрабатывает работу вызовов. Это может быть библиотека времени выполнения, виртуальная машина или операционная система. Конечно, код не одинаково доступен во всех случаях.

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

Итак, предположим, что вы делаете три распределения и де-распределения и получаете блоки, выложенные в памяти в следующем порядке:

+-+-+-+
|A|B|C|
+-+-+-+

размеры отдельных распределений не имеют значения. затем вы освобождаете первый и последний, A и C:

+-+-+-+
| |B| |
+-+-+-+

когда Вы наконец освободите B, вы (изначально, по крайней мере теоретически) получите:

+-+-+-+
| | | |
+-+-+-+

которые могут быть разделены на просто

+-+-+-+
|     |
+-+-+-+

т. е. один большой свободный блок, нет фрагменты остались.

ссылки, как и просили:

  • попробуйте прочитать код dlmalloc. Я намного более продвинутый, будучи полной реализацией качества производства.
  • даже во встроенных приложениях доступны де-фрагментирующие реализации. См., например,эти заметки на heap4.c код в FreeRTOS.

другие ответы уже прекрасно объясняют, что реальной реализации malloc() и free() действительно объединяют (дефрагментируют) отверстия в более крупные свободные куски. Но даже если бы это было не так, все равно было бы плохой идеей отказаться free().

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

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

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

Это все еще плохая практика конечно (повышение производительности всегда должно основываться на профилировании, а не на смутном чувстве кишки), и это не то, что вы должны сказать студентам, не объясняя другие ограничения, но я могу представить себе множество крошечных приложений-оболочек-оболочек, которые будут написаны таким образом (если не использовать статическое распределение напрямую). Если вы работаете над чем-то, что выигрывает от не освобождения ваших переменных, вы либо работаете в экстремальных условиях с низкой задержкой (в этом случае, как вы можете даже позволить себе динамические выделение и C++? :D), или вы делаете что-то очень, очень неправильно (например, выделяя целочисленный массив, выделяя тысячу целых чисел один за другим, а не один блок памяти).

Вы сказали, что они были профессорами электроники. Они могут быть использованы для написания прошивки / программного обеспечения в реальном времени, были в состоянии точно определить время выполнения чего-то часто требуется. В этих случаях, зная, что у вас достаточно памяти для всех выделений, а не освобождение и перераспределение памяти может дать более легко вычисляемый предел времени выполнения.

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

статическое распределение происходит, когда вы делаете такие вещи, как:

define MAX_SIZE 32
int array[MAX_SIZE];

во многих системах реального времени и встраиваемых системах(которые, скорее всего, столкнутся с EEs или CEs), обычно предпочтительно вообще избегать динамического выделения памяти. Так, использование malloc,new, и их аналоги удаления редки. Кроме того, память в компьютерах взорвалась в последние годы.

если у вас есть 512 МБ, и вы статически выделяете 1 МБ, у вас есть примерно 511 МБ для трандлирования, прежде чем ваше программное обеспечение взорвется(ну, не совсем так...но идите со мной сюда). Предполагая, что у вас есть 511 МБ для злоупотребления, если вы malloc 4 байта каждую секунду, не освобождая их, вы сможете работать в течение почти 73 часов перед запуском нехватка памяти. Учитывая, что многие машины отключаются один раз в день, это означает, что ваша программа никогда не будет работать из памяти!

В приведенном выше примере, утечка составляет 4 байт в секунду или 240 байт/мин. Теперь представьте, что вы уменьшаете это соотношение байт/мин. Чем ниже это соотношение, тем дольше ваша программа может работать без проблем. Если ваш mallocs нечасты, это реальная возможность.

черт возьми, если вы знаете, что вы только собираетесь malloc что-то один раз, и то malloc никогда не будет поражен снова, тогда это очень похоже на статическое распределение, хотя вам не нужно знать размер того, что вы выделяете заранее. Например: допустим, у нас снова есть 512 МБ. Нам нужно malloc 32 массива целых чисел. Это типичные целые числа-по 4 байта каждое. Мы знаем, что размеры этих массивов никогда не будут превышать 1024 целых чисел. Никакие другие выделения памяти не происходят в нашей программе. У нас достаточно памяти? 32 * 1024 * 4 = 131,072. 128 КБ-так что да. У нас достаточно места. Если мы знаем мы никогда не будем выделять больше памяти, мы можем безопасно malloc эти массивы, не освобождая их. Однако это может также означать, что вам придется перезагрузить компьютер/устройство, если ваша программа выйдет из строя. Если вы запустите / остановите свою программу 4,096 раза, вы выделите все 512 МБ. Если у вас есть зомби-процессы, возможно, что память никогда не будет освобождена, даже после сбоя.

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

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

malloc() в конечном итоге вызовет либо mmap (), либо sbrk (), который получит страницу из ОС.

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

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

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

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

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

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

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

большинство программ ведут себя плохо. Они выделяют и освобождают память более или менее случайным образом, в различных размерах от очень маленьких до очень больших, и они сохраняют высокое использование выделенных блоков. В этих программах способность к объединению блоков ограничена и со временем они заканчивают с памятью сильно фрагментированной и относительно несмежные. Если общее использование памяти превышает около 1,5 ГБ в 32-разрядном пространстве памяти, и есть выделения (скажем) 10 Мб или более, в конечном итоге один из больших выделений не удастся. Эти программы являются общими.

другие программы освобождают мало или нет памяти, пока они не остановятся. Они постепенно выделяют память во время работы, освобождая только небольшие количества, а затем останавливаются, и в это время вся память освобождается. Компилятор такой. Так же как и виртуальная машина. Например, среда CLR .NET среда выполнения, сама написанная на C++, вероятно, никогда не освобождает память. С чего бы это?

и это окончательный ответ. В тех случаях, когда программа достаточно тяжелая в использование памяти, управление памятью через malloc и free не является достаточным ответом на вопрос. Если вы не достаточно удачливы, чтобы иметь дело с корректной программой, вам нужно будет разработать один или несколько пользовательских распределителей памяти, которые предварительно выделяют большие куски памяти, а затем выделяют в соответствии с стратегия на ваш выбор. Вы не можете использовать free вообще, за исключением случаев, когда программа останавливается.

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

EDIT

Я попробую ответить на некоторые критические замечания. Очевидно, что это не очень хорошее место для сообщений такого рода. Просто чтобы быть ясным: у меня есть около 30 лет опыта написания такого рода программного обеспечения, включая пару компиляторов. У меня нет академических рекомендаций, только мои собственные синяки. Я не могу избавиться от ощущения, что критика исходит от людей с гораздо более узким и коротким опытом.

Я повторю свое ключевое сообщение: балансировка malloc и free-это не достаточное решение для крупномасштабного выделения памяти в реальных программах. Блок коалесцирует нормально, и покупает время, но это не

Я удивлен, что никто не цитирует Книги еще:

Это может быть не так в конечном итоге, потому что память может получить достаточно большой, так что было бы невозможно запустить из свободной памяти в течение всего срока службы компьютера. Например, есть около 3 ⋅ 1013 микросекунды в год, так что если бы мы были минусы один раз в микросекунду нам понадобится около 1015 ячейки памяти для создания машины, которая могла бы работать в течение 30 лет без нехватки памяти. Такое количество памяти кажется абсурдно большим по сегодняшним меркам, но это не физически невозможно. С другой стороны, процессоры становятся быстрее, и будущий компьютер может иметь большое количество процессоров, работающих параллельно на одной памяти, поэтому может быть возможно использовать память намного быстрее, чем мы постулировали.

http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298

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

Я знаю об одном случае, когда явного освобождения памяти хуже, чем бесполезно. То есть, когда вам нужны все ваши данные до конца пожизненного процесса. Другими словами, при освобождении их возможно только непосредственно перед завершением программы. Поскольку любая современная ОС заботится об освобождении памяти, когда программа умирает, вызывая free() в этом случае нет необходимости. Фактически, это может замедлить завершение программы, так как может потребоваться доступ к нескольким страницам в памяти.