Хорошо ли использовать * not * использовать free() в выделенной памяти?

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

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

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

Ответ 1

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

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

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

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

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

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

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

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

который можно разделить на просто

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

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

Ссылки по запросу:

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

Ответ 2

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

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

Ответ 3

Это полная бессмыслица, например, существует множество различных реализаций malloc, некоторые пытаются сделать кучу более эффективной, например Doug Lea или this one.

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

define MAX_SIZE 32
int array[MAX_SIZE];

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

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

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

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

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

ИЗМЕНИТЬ

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

Я повторю свое ключевое сообщение: балансировка malloc и free не - достаточное решение для распределения больших объемов памяти в реальных программах. Блок коалесценции является нормальным, и покупает время, но он не. Вам нужны серьезные, умные распределители памяти, которые, как правило, захватывают память в кусках (используя malloc или что-то еще) и редко пользуются. Это, вероятно, сообщение, которое имели в виду профессора OP, которые он неправильно понял.

Ответ 10

Я удивлен, что никто еще не процитировал The Book:

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

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

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

Ответ 11

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