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


66

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

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

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

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

Изменить: вопрос, предложенный в качестве дубликата, был характерен для семейства операционных систем Unix. В его верхнем ответе даже указан инструмент, специфичный для Linux (например, Valgrind). Этот вопрос предназначен для охвата большинства «обычных» не встроенных операционных систем, а также того, почему стоит или не рекомендуется освобождать память, которая необходима на протяжении всей жизни программы.



19
Вы пометили этот язык как независимый, но во многих языках (например, Java) нет необходимости вообще освобождать память вручную; это происходит автоматически через некоторое время после того, как последняя ссылка на объект выходит из области видимости
Ричард Тингл

3
и, конечно, вы можете написать C ++ без единого удаления, и это будет хорошо для памяти
Nikko

5
@RichardTingle Хотя я не могу думать ни о каком другом языке, кроме C и, возможно, C ++, тег, не зависящий от языка, предназначался для охвата всех языков, в которых нет встроенных утилит для сбора мусора. Это по общему признанию редкость в настоящее время все же. Вы можете утверждать, что теперь вы можете реализовать такую ​​систему в C ++, но в конечном итоге у вас все еще есть возможность не удалять часть памяти.
CaptainObvious

4
Вы пишете: «Ядро в основном гарантирует очистку всех ресурсов [...] после завершения программы». Обычно это неверно, потому что не все является памятью, дескриптором или объектом ядра. Но это верно для памяти, которой вы немедленно ограничиваете свой вопрос.
Эрик Тауэрс

Ответы:


107

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

Это не обязательно, но может иметь свои преимущества (а также некоторые недостатки).

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

Однако, освобождая всю память в конце выполнения явно,

  • во время отладки / тестирования инструменты обнаружения утечки памяти не будут показывать «ложные срабатывания»
  • может быть намного проще переместить код, который использует память вместе с выделением и освобождением, в отдельный компонент и использовать его позже в другом контексте, где пользователь компонента должен контролировать время использования памяти

Продолжительность жизни программ может измениться. Возможно, ваша программа сегодня - это небольшая утилита командной строки, с типичным временем жизни менее 10 минут, и она выделяет память порциями по несколько килобайт каждые 10 секунд - поэтому не нужно вообще освобождать выделенную память до завершения программы. Позже программа изменяется и получает расширенное использование как часть серверного процесса со сроком службы в несколько недель - поэтому не освобождается промежуточная неиспользуемая память больше не вариант, иначе ваша программа со временем начинает поглощать всю доступную серверную память , Это означает, что вам придется просмотреть всю программу и впоследствии добавить освобождающий код. Если вам повезет, это простая задача, в противном случае это может быть настолько сложно, что высоки шансы, что вы пропустите место. И когда вы окажетесь в такой ситуации, вам будет жаль, что вы добавили «бесплатно»

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


12
Хорошая мысль о разработке хороших навыков программирования.
Лоуренс

4
Вообще я полностью согласен с этим советом. Сохранение хороших привычек с памятью очень важно. Тем не менее, я думаю, что есть исключения. Например, если вы выделили какой-то сумасшедший график, который займет несколько секунд, чтобы пройти и освободить «должным образом», то вы бы улучшили пользовательский опыт, просто завершив программу и позволив ОС обнулить ее.
GrandOpener

1
Это безопасно на любой современной «нормальной» (не встроенной с защитой памяти) ОС и было бы серьезной ошибкой, если бы не было. Если непривилегированный процесс может навсегда потерять память, которую ОС не сможет восстановить, то при многократном запуске он может запустить систему из памяти. Долгосрочная работоспособность ОС не может зависеть от отсутствия ошибок в непривилегированных программах. (конечно, это игнорирование таких вещей, как сегменты разделяемой памяти Unix, которые в лучшем случае поддерживаются только пространством подкачки.)
Питер Кордес,

7
@GrandOpener В этом случае вы можете предпочесть использовать какой-то тип распределителя на основе региона для этого дерева, чтобы вы могли распределить его обычным способом и просто освободить весь регион сразу, когда придет время, вместо того, чтобы обходить его и освобождать это по крупицам. Это все еще "правильно".
Томас,

3
Кроме того, если память действительно должна существовать в течение всего времени жизни программы, это может быть разумной альтернативой для создания структуры в стеке, например, в main.
Кайл Стрэнд,

11

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

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

Одним из разумных решений для этого является «talloc», который позволяет вам загружать память и затем отбрасывать их все одним вызовом.


1
Предполагая, что вы знаете, что ваша ОС не имеет собственных утечек.
WGroleau

5
«трата процессорного времени» Хотя очень и очень мало времени. freeобычно намного быстрее чем malloc.
Пол Дрэйпер

@PaulDraper Да, пустая трата времени на подкачку гораздо важнее.
Дедупликатор

5

Вы можете использовать язык со сборщиком мусора (такой как Scheme, Ocaml, Haskell, Common Lisp и даже Java, Scala, Clojure).

(В большинстве языков GC-й изд, нет никакого способа , чтобы явно и вручную свободная память Иногда, некоторые значения могут быть! Завершено , например, GC и выполнение система закроет значение дескриптора файла , когда это значение недостижимо, но это не конечно, ненадежно, и вы должны вместо этого явно закрыть свои файловые дескрипторы, так как завершение никогда не гарантируется)

Вы также можете, для вашей программы, написанной на C (или даже C ++) использовать консервативный сборщик мусора от Boehm . Затем вы бы заменили все ваши mallocна GC_malloc и не беспокоиться о free-ing любой указатель. Конечно, вы должны понимать плюсы и минусы использования Boehm GC. Читайте также руководство GC .

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

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

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

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

Обратите внимание, что система freeчасто не освобождает память для ОС (например, вызывая munmap (2) в системах POSIX), но обычно помечает зону памяти как пригодную для повторного использования в будущем malloc. В частности, виртуальное адресное пространство (например, как видно /proc/self/mapsиз Linux, см. Proc (5) ....) может не уменьшаться после free(следовательно, утилиты любят psили topсообщают о том же объеме используемой памяти для вашего процесса).


3
«Иногда некоторые значения могут быть завершены, например, GC и система времени выполнения закрывают значение дескриптора файла, когда это значение недоступно». Вы можете подчеркнуть, что в большинстве языков GCed нет никаких гарантий, что память когда-либо будет восстановлена, и любой финализатор когда-либо запускался, даже при выходе: stackoverflow.com/questions/7880569/…
Deduplicator

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

3
@tathanhdinh Более автоматизированный, но исключительно для памяти. Полностью ручной, для всех других ресурсов. GC торгует детерминизмом и памятью для удобного управления памятью. И нет, финализаторы мало помогают, и у них есть свои проблемы.
Дедупликатор

3

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

Один из самых мощных случаев, с которыми я сталкиваюсь (снова и снова), - это то, что кто-то пишет небольшой фрагмент кода моделирования, который выполняется в их исполняемом файле. Они говорят: «Мы бы хотели, чтобы этот код был интегрирован в симуляцию». Затем я спрашиваю их, как они планируют переинициализироваться между пробегами Монте-Карло, и они смотрят на меня безучастно. «Что вы имеете в виду, повторная инициализация? Вы просто запускаете программу с новыми настройками?»

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

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


1

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

То, что вы сейчас считаете «программой», может в какой-то момент стать просто функцией или методом, который является частью более крупной программы. И тогда эта функция может вызываться несколько раз. И тогда память, которую вы должны были «освободить вручную», станет утечкой памяти. Это, конечно, решение суда.


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

1

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

Делайте вещи, пока ваше понимание о них еще свежо.

Это небольшие инвестиции, которые могут принести большую прибыль в будущем.


2
это, кажется, не добавляет ничего существенного по поводу высказанных и объясненных в предыдущих 11 ответах. В частности, пункт о возможном изменении программы в будущем уже был сделан три или четыре раза
комнат

1
@gnat Запутанным и запутанным способом - да. В четком изложении - нет. Давайте сосредоточимся на качестве ответа, а не на скорости.
Agent_L

1
Мудрый, лучший ответ, кажется, гораздо лучше объяснить этот момент
комнат

1

Нет, это не обязательно, но это хорошая идея.

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

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

Конечно, это дает дополнительное преимущество, так как ваш код может быть повторно использован и адаптирован, как уже говорили другие.

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

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

Если вместо этого программа просто прерывается, ОС просто отбрасывает всю память, которая была перенесена на диск, что почти мгновенно, потому что она вообще не требует доступа к диску.

Важно отметить, что в языке OO вызов деструктора объекта также заставит обменяться памятью; если вам нужно сделать это, вы можете также освободить память.


1
Можете ли вы привести что-то, чтобы поддержать идею, что для освобождения нужно использовать выгружаемую память? Это явно неэффективно, конечно, современные ОС умнее этого!

1
@ John of All Trades, современные ОС умные, но по иронии судьбы большинство современных языков - нет. Когда вы освобождаете (что-то), компилятор помещает «что-то» в стек, а затем вызывает free (). Чтобы поместить его в стек, необходимо вернуть его обратно в оперативную память, если он был выгружен.
Джеффкинс

@JonofAllTrades free-list будет иметь такой эффект, хотя это довольно устаревшая реализация malloc. Но ОС не может помешать вам использовать такую ​​реализацию.

0

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

Основными причинами, по которым не нужно выполнять очистку вручную, являются: производительность, производительность, производительность и возможность некоторой ошибки «два раза бесплатно» или «использование после освобождения» в ненужном коде очистки, которая может превратиться в ошибку сбоя или безопасность эксплуатируют.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.