Можно ли * не * использовать free () для выделенной памяти?

83

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

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

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

Ник
источник
11
Они не «неправы» - у них есть действительный (если ограниченный) пункт о фрагментации очень маленьких изолированных свободных областей, и, вероятно, они изложены немного более тщательно, чем вы сообщили об этом.
Крис Страттон,
2
Единственный раз, когда вам не нужно освобождать память, - это при использовании управляемой памяти или когда вы собираетесь повторно использовать исходную выделенную память. Я подозреваю, что причина, по которой два инструктора сказали это, заключается в том, что вы говорите о конкретной программе, которая может повторно использовать память. В этом случае вы все равно будете использовать free (), но только в конце вашего приложения. Вы уверены, что они имели в виду не это?
Krowe
17
@Marian: У меня был профессор, который утверждал, что в C и C ++ необходимо освободить выделенную память в функции, определенной в том же файле .c / .cxx, в котором она была выделена ... Эти люди иногда, кажется, сильно страдают от гипоксии из-за того, что жили слишком высоко в башне из слоновой кости.
PlasmaHH
4
Существует довольно много программ, не связанных с игрушками, которые не освобождают память, и позволить ОС очистить все это при выходе из процесса намного быстрее, чем (суетливо) вести большую бухгалтерию, чтобы вы могли сделать это самостоятельно.
Donal Fellows
9
Никогда не сливайте услышанное в мозг без вопросов. У меня было много учителей, лекторов и корректоров, которые ошибались или устарели. И всегда очень точно анализируйте то, что они говорят. Наши люди часто довольно точны и могут говорить то, что правильно, но легко понять неправильно или с неправильным приоритетом тем, кто чувствует себя как дома только в просторечии. Например, я помню, в школе учитель сказал: «Ты сделал домашнее задание?», Я сказал: «Нет». Хотя я был прав, учитель нашел это оскорблением, потому что я сэкономил мне время на поиски неубедительных оправданий, которых он не ожидал.
Себастьян Мах

Ответы:

100

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

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

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

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

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

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

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

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

который можно дефрагментировать только на

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

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

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

  • Попробуйте прочитать код для dlmalloc . Я намного более продвинутый, будучи полной реализацией производственного качества.
  • Даже во встроенных приложениях доступны реализации дефрагментации. См., Например, эти примечания по heap4.cкоду FreeRTOS .
размотать
источник
1
Вы можете дать мне несколько рекомендаций?
Ник
4
Я думаю, стоит упомянуть, что виртуальное адресное пространство не является прямым представлением физической памяти. И с этой фрагментацией в физической памяти может справиться ОС, в то время как виртуальная память, не освобожденная процессом, также не будет освобождена физически.
лапк
@PetrBudnik, в любом случае виртуальная память сопоставляет 1-1 с физической памятью редко, ОС будет думать о сопоставлении страниц и сможет с минимальной суетой менять местами их вставку и отключение
ratchet freak
3
Очень придирчивый комментарий: Хотя концепция хорошо объяснена, фактический пример был выбран немного ... неудачно. Для тех, кто смотрит на исходный код, скажем, dlmalloc и запутывается: блоки ниже определенного размера всегда имеют степень двойки и соответственно объединяются / разделяются. Таким образом, мы получим (возможно) один 8-байтовый блок и 1 4-байтовый блок, но не 12-байтовый блок. Это довольно стандартный подход для распределителей памяти, по крайней мере, на настольных компьютерах, хотя, возможно, встроенные приложения стараются более осторожно относиться к своим накладным расходам.
Voo
@Voo Я убрал в примере упоминание о размере блоков, все равно это не имело значения. Лучше?
расслабьтесь
42

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

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

Энгью больше не гордится SO
источник
6
+1 Совершенно верно. Дело в том, что если он freeвызывается достаточно раз, чтобы повлиять на производительность, то, вероятно, он также вызывается достаточное количество раз, так что его отсутствие приведет к очень большой вмятине в доступной памяти. Трудно представить себе ситуацию во встроенной системе, в которой производительность постоянно снижается из-за, freeно где mallocвызывается только конечное число раз; Это довольно редкий вариант использования встроенного устройства, которое выполняет однократную обработку данных, а затем перезагружается.
Jason C
10

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

Пол Эванс
источник
9

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

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

Луаан
источник
Предоставление ОС освободить все в конечном итоге - это не только производительность - для работы требуется гораздо меньше логики.
hugomg
@missingno Другими словами, это позволяет вам уйти от того, насколько жестким может быть управление памятью :) Тем не менее, я бы увидел это как аргумент против неуправляемых языков - если ваша причина - сложная логика освобождения, а не производительность, вам может быть лучше не использовать язык / среду, которая позаботится об этом за вас.
Luaan,
5

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

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

Пэдди3118
источник
10
Неплохо подмечено. Однако я ожидал, что в этом случае они вообще не будут использовать mallocи тому подобное, вместо этого полагаясь на статическое выделение (или, возможно, выделят большой кусок, а затем вручную обработают память).
Luaan
2

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

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

define MAX_SIZE 32
int array[MAX_SIZE];

Во многих системах реального времени и встроенных системах (с которыми чаще всего сталкиваются EE или CE) обычно предпочтительно вообще избегать динамического распределения памяти. Итак, использование malloc,new и их удаления коллеги редко. Вдобавок ко всему в последние годы произошел взрывной рост памяти в компьютерах.

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

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

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

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

Шаз
источник
2
В большинстве встроенных систем и систем реального времени серьезной проблемой будет бомба замедленного действия, которая приведет к отказу всего через 73 часа.
Ben Voigt
типичные целые числа ??? Целое число - это как минимум 16-битное число, а на небольших микрочипах обычно 16-битное. Как правило, устройств с sizeof(int)равным 2 больше, чем 4.
ST3
2

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

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

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

mfro
источник
2

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

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

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

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

Мэтью Уолтон
источник
1

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

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

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

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

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

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

РЕДАКТИРОВАТЬ

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

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

david.pfx
источник
1
Программы с плохим поведением показывают, что достойные распределители должны использовать отдельные пулы для блоков разного размера. С виртуальной памятью наличие большого количества разных пулов, находящихся далеко друг от друга, не должно быть большой проблемой. Если каждый фрагмент округляется до степени двойки, и каждый пул содержит округленные фрагменты только одного размера, я не могу понять, как фрагментация может стать очень плохой. В худшем случае программа может внезапно вообще перестать интересоваться определенным диапазоном размеров, оставляя некоторые в основном пустые пулы неиспользованными; Я не думаю, что это очень распространенное поведение.
Marc van Leeuwen
5
Цитата очень необходима для утверждения, что грамотно написанные программы не освобождают память, пока приложение не закрывается.
12
Весь этот ответ читается как серия случайных догадок и предположений. Есть ли что-нибудь для подтверждения этого?
Chris Hayes
4
Я не совсем понимаю, что вы имеете в виду, говоря, что среда выполнения .NET CLR не освобождает память. Насколько я тестировал, да, если может.
Теодорос Хатцигианнакис,
1
@vonbrand: GCC имеет несколько распределителей, включая сборщик мусора собственной марки. Он потребляет память во время проходов и собирает ее между проходами. Большинство компиляторов для других языков имеют не более 2 проходов и мало или совсем не освобождают память между проходами. Если вы не согласны, я буду рад изучить любой приведенный вами пример.
david.pfx
1

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

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

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

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

дуб
источник
1

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

Миклош Хомоля
источник