Я изучаю компьютерную инженерию, и у меня есть курсы электроники. Я слышал от двух моих профессоров (из этих курсов) , что можно избежать с помощью free()
функции (после того, как malloc()
, calloc()
и т.д.) , так как пространства памяти , выделенные , вероятно , не будет использоваться снова , чтобы выделить другую память. То есть, например, если вы выделите 4 байта, а затем освободите их, у вас будет 4 байта пространства, которое, вероятно, больше не будет выделено: у вас будет дыра .
Я думаю, это безумие: у вас не может быть не-игрушечной программы, в которой вы выделяете память в куче, не освобождая ее. Но у меня нет знаний, чтобы точно объяснить, почему так важно, чтобы для каждого malloc()
был free()
.
Итак: существуют ли обстоятельства, при которых было бы целесообразно использовать malloc()
без использования free()
? А если нет, то как я могу объяснить это своим профессорам?
Ответы:
Легко: просто прочтите исходники практически любой полусерьезной
malloc()/free()
реализации. Под этим я имею в виду реальный менеджер памяти, который обрабатывает работу с вызовами. Это может быть библиотека времени выполнения, виртуальная машина или операционная система. Конечно, не во всех случаях код одинаково доступен.Очень часто бывает очень часто следить за тем, чтобы память не фрагментировалась, путем объединения соседних отверстий в более крупные. Более серьезные распределители используют более серьезные методы, чтобы гарантировать это.
Итак, предположим, что вы выполняете три выделения и отмены выделения и размещаете блоки в памяти в следующем порядке:
Размеры отдельных выделений не имеют значения. затем вы освобождаете первый и последний, A и C:
когда вы наконец освобождаете B, вы (изначально, по крайней мере теоретически) получаете:
который можно дефрагментировать только на
т.е. один свободный блок большего размера, не осталось фрагментов.
Ссылки по запросу:
heap4.c
коду FreeRTOS .источник
Другие ответы уже прекрасно объясняют, что реальные реализации
malloc()
иfree()
действительно объединяют (дефрагментируют) дыры в большие свободные фрагменты. Но даже если бы это было не так, отказываться от этого все равно было бы плохой идеейfree()
.Дело в том, что ваша программа только что выделила (и хочет освободить) эти 4 байта памяти. Если он будет работать в течение длительного периода времени, вполне вероятно, что ему снова потребуется выделить всего 4 байта памяти. Таким образом, даже если эти 4 байта никогда не объединятся в большее непрерывное пространство, они все равно могут быть повторно использованы самой программой.
источник
free
вызывается достаточно раз, чтобы повлиять на производительность, то, вероятно, он также вызывается достаточное количество раз, так что его отсутствие приведет к очень большой вмятине в доступной памяти. Трудно представить себе ситуацию во встроенной системе, в которой производительность постоянно снижается из-за,free
но гдеmalloc
вызывается только конечное число раз; Это довольно редкий вариант использования встроенного устройства, которое выполняет однократную обработку данных, а затем перезагружается.Это полная чушь, например, есть много разных реализаций
malloc
, некоторые пытаются сделать кучу более эффективной, как у Дуга Ли или этой .источник
Ваши профессора случайно не работают с POSIX? Если они привыкли писать множество небольших, минималистичных приложений оболочки, это сценарий, при котором я могу представить, что этот подход был бы не так уж и плох - освобождение всей кучи сразу в свободное время ОС - это быстрее , чем освободив тысяча переменных. Если вы ожидаете, что ваше приложение будет работать в течение секунды или двух, вы можете легко обойтись без отмены выделения.
Конечно, это все еще плохая практика (улучшения производительности всегда должны основываться на профилировании, а не на неопределенном внутреннем ощущении), и это не то, что вы должны говорить студентам, не объясняя другие ограничения, но я могу представить себе множество крошечных оболочек -приложения должны быть написаны таким образом (если не использовать статическое размещение напрямую). Если вы работаете над тем, что не освобождает ваши переменные, вы либо работаете в условиях крайне низкой задержки (в этом случае, как вы можете позволить себе даже динамическое размещение и C ++?: D), либо вы делать что-то очень и очень неправильное (например, выделить целочисленный массив путем размещения тысячи целых чисел одно за другим, а не одного блока памяти).
источник
Вы упомянули, что они были профессорами электроники. Их можно использовать для написания прошивок / программного обеспечения в реальном времени, поскольку часто требуется точное время выполнения некоторых задач. В этих случаях знание того, что у вас достаточно памяти для всех распределений, а также отсутствие освобождения и перераспределения памяти может дать более легко вычисляемый предел времени выполнения.
В некоторых схемах может также использоваться аппаратная защита памяти, чтобы убедиться, что подпрограмма завершается в выделенной ей памяти, или генерирует ловушку в очень исключительных случаях.
источник
malloc
и тому подобное, вместо этого полагаясь на статическое выделение (или, возможно, выделят большой кусок, а затем вручную обработают память).Если рассматривать это под другим углом, чем предыдущие комментаторы и ответы, одна из возможностей состоит в том, что ваши преподаватели имели опыт работы с системами, в которых память распределялась статически (то есть: когда программа была скомпилирована).
Статическое распределение происходит, когда вы делаете что-то вроде:
define MAX_SIZE 32 int array[MAX_SIZE];
Во многих системах реального времени и встроенных системах (с которыми чаще всего сталкиваются EE или CE) обычно предпочтительно вообще избегать динамического распределения памяти. Итак, использование
malloc
,new
и их удаления коллеги редко. Вдобавок ко всему в последние годы произошел взрывной рост памяти в компьютерах.Если вам доступно 512 МБ, и вы статически выделяете 1 МБ, у вас есть примерно 511 МБ, которые нужно перебрать, прежде чем ваше программное обеспечение взорвется (ну, не совсем ... но идите со мной сюда). Предполагая, что у вас есть 511 МБ для злоупотреблений, если вы выделяете 4 байта каждую секунду, не освобождая их, вы сможете работать в течение почти 73 часов, прежде чем у вас закончится память. Учитывая, что многие машины выключаются один раз в день, это означает, что вашей программе никогда не хватит памяти!
В приведенном выше примере утечка составляет 4 байта в секунду или 240 байтов / мин. Теперь представьте, что вы уменьшили это соотношение байт / мин. Чем ниже это соотношение, тем дольше ваша программа может работать без проблем. Если ваши
malloc
s нечастые, это реальная возможность.Черт возьми, если вы знаете, что собираетесь к
malloc
чему-то только один раз, и этоmalloc
никогда не будет повторяться, тогда это очень похоже на статическое распределение, хотя вам не нужно знать размер того, что вы выделяете - фронт. Например: Допустим, у нас снова 512 МБ. Нам нужноmalloc
32 массива целых чисел. Это типичные целые числа - по 4 байта каждое. Мы знаем, что размеры этих массивов никогда не превышают 1024 целых числа. Никакого другого выделения памяти в нашей программе не происходит. Достаточно ли у нас памяти? 32 * 1024 * 4 = 131072. 128 КБ - так что да. У нас много места. Если мы знаем, что больше никогда не будем выделять память, мы можем безопасноmalloc
эти массивы, не освобождая их. Однако это также может означать, что вам придется перезагрузить компьютер / устройство, если ваша программа выйдет из строя. Если вы запустите / остановите свою программу 4096 раз, вы выделите все 512 МБ. Если у вас есть зомби-процессы, возможно, память никогда не будет освобождена даже после сбоя.Сэкономить боль и страдание, и потреблять эту мантру как единая истина:
malloc
должны всегда быть связанно сfree
. всегдаnew
должен быть .delete
источник
sizeof(int)
равным 2 больше, чем 4.Я думаю, что утверждение, изложенное в вопросе, является чепухой, если воспринимать его буквально с точки зрения программиста, но оно имеет истину (по крайней мере, некоторую) с точки зрения операционной системы.
malloc () в конечном итоге вызовет либо mmap (), либо sbrk (), который получит страницу из ОС.
В любой нетривиальной программе шансы, что эта страница когда-либо будет возвращена ОС в течение жизненного цикла процесса, очень мала, даже если вы free () используете большую часть выделенной памяти. Таким образом, память free () 'd большую часть времени будет доступна только одному и тому же процессу, но не другим.
источник
Ваши профессора не ошибаются, но тоже ошибаются (они, по крайней мере, вводят в заблуждение или упрощают). Фрагментация памяти вызывает проблемы с производительностью и эффективным использованием памяти, поэтому иногда вам все же нужно учитывать это и принимать меры, чтобы этого избежать. Один из классических приемов заключается в том, что если вы выделяете много объектов одинакового размера, при запуске захватываете пул памяти, который в несколько раз больше этого размера, и полностью управляете его использованием изнутри, что гарантирует отсутствие фрагментации в Уровень ОС (и дыры в вашем картографе внутренней памяти будут точно того размера, который подходит для следующего объекта этого типа, который появится).
Существуют целые сторонние библиотеки, которые ничего не делают, кроме как обрабатывают такие вещи за вас, и иногда это разница между приемлемой производительностью и тем, что работает слишком медленно.
malloc()
иfree()
потребуют заметного количества времени для выполнения, которое вы начнете замечать, если будете им часто звонить.Таким образом , избегая только наивности использования
malloc()
иfree()
вы можете избежать как осколочные и проблем производительности - но когда вы получите прямо к ней, вы всегда должны убедиться , что выfree()
все , что ,malloc()
если у вас есть очень веские причины , чтобы сделать иначе. Даже при использовании пула внутренней памяти хорошее приложение будетfree()
использовать пул памяти перед своим выходом. Да, ОС очистит его, но если впоследствии жизненный цикл приложения изменится, будет легко забыть, что этот пул все еще существует ...Разумеется, долго работающие приложения должны очень скрупулезно относиться к очистке или переработке всего, что они выделили, иначе у них закончится нехватка памяти.
источник
Ваши профессора поднимают важный вопрос. К сожалению, английский язык используется так, что я не совсем уверен, что они говорят. Позвольте мне ответить на этот вопрос с точки зрения неигровых программ, которые имеют определенные характеристики использования памяти и с которыми я лично работал.
Некоторые программы хорошо себя ведут. Они распределяют память волнообразно: множество небольших или средних распределений, за которыми следует множество освобождений в повторяющихся циклах. В этих программах неплохо работают типичные распределители памяти. Они объединяют освобожденные блоки, и в конце волны большая часть свободной памяти оказывается большими непрерывными блоками. Эти программы довольно редки.
Большинство программ плохо себя ведут. Они распределяют и освобождают память более или менее случайным образом, в различных размерах от очень маленького до очень большого, и они сохраняют высокий уровень использования выделенных блоков. В этих программах возможность объединения блоков ограничена, и со временем они заканчивают тем, что память становится сильно фрагментированной и относительно несмежной. Если общее использование памяти превышает примерно 1,5 ГБ в 32-разрядном пространстве памяти, а выделено (скажем) 10 МБ или более, в конечном итоге одно из больших выделений выйдет из строя. Эти программы распространены.
Другие программы освобождают мало или совсем не освобождают память, пока не остановятся. Они постепенно выделяют память во время работы, освобождая только небольшие ее объемы, а затем останавливаются, после чего вся память освобождается. Компилятор такой. Как и ВМ. Например, среда выполнения .NET CLR, написанная на C ++, вероятно, никогда не освобождает память. Зачем это нужно?
И это окончательный ответ. В тех случаях, когда программа использует достаточно много памяти, управление памятью с помощью malloc и free не является достаточным решением проблемы. Если вам не повезло иметь дело с хорошо работающей программой, вам нужно будет разработать один или несколько пользовательских распределителей памяти, которые предварительно выделяют большие фрагменты памяти, а затем распределяют их в соответствии с выбранной вами стратегией. Вы не можете использовать бесплатно вообще, кроме случаев, когда программа останавливается.
Не зная точно, что сказали ваши профессора, для действительно масштабных программ я, вероятно, выступил бы на их стороне.
РЕДАКТИРОВАТЬ
Я постараюсь ответить на некоторые критические замечания. Очевидно, SO - не лучшее место для постов такого рода. Для ясности: у меня около 30 лет опыта написания такого рода программ, включая пару компиляторов. У меня нет академических рекомендаций, только мои собственные синяки. Я не могу избавиться от ощущения, что критика исходит от людей с гораздо более узким и меньшим опытом.
Я повторю свое ключевое сообщение: балансировка malloc и free не является достаточным решением для крупномасштабного распределения памяти в реальных программах. Слипания блоков нормально, и выиграть время, но это не достаточно. Вам нужны серьезные, умные распределители памяти, которые, как правило, захватывают память частями (используя malloc или что-то еще) и редко освобождают. Вероятно, это сообщение имели в виду профессора ОП, которое он неправильно понял.
источник
Я удивлен, что никто еще не процитировал Книгу :
http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298
Итак, действительно, многие программы могут работать нормально, даже не беспокоясь об освобождении памяти.
источник
Я знаю об одном случае, когда явное освобождение памяти хуже, чем бесполезно . То есть, когда вам нужны все ваши данные до конца жизненного цикла процесса. Другими словами, при их освобождении возможно только непосредственно перед завершением программы. Поскольку любая современная ОС заботится об освобождении памяти, когда программа умирает,
free()
в этом случае вызывать не нужно. Фактически, это может замедлить завершение программы, так как может потребоваться доступ к нескольким страницам в памяти.источник