Как далеко могут зайти утечки памяти?

118

Я много раз сталкивался с утечками памяти. Обычно, когда я ухожу, mallocкак будто завтра не наступит, или болтаюсь, FILE *как грязное белье. Обычно я предполагаю (читай: отчаянно надеюсь), что вся память очищается, по крайней мере, когда программа завершается. Существуют ли ситуации, когда утечка памяти не будет собрана при завершении или сбое программы?

Если ответ сильно различается от языка к языку, давайте сосредоточимся на C (++).

Обратите внимание на гиперболическое употребление фраз «как будто завтра не наступит» и «болтается ... как грязное белье». Небезопасность mallocможет навредить тем, кого любишь. Также будьте осторожны с грязным бельем.

DilithiumMatrix
источник
3
Если вы работаете с «современной» ОС, такой как Linux или Windows, тогда ОС сама устранит всю невыделенную память при завершении программы.
Оливер Чарльзуорт,
60
Вместо того, чтобы делать вид, будто завтра не наступит, попробуйте притвориться, что завтра наступит, и следите за своей памятью!
Уильям Перселл
8
@WilliamPursell ах, значит, вы говорите, нужно думать, что callocзавтра не будет. Превосходно.
DilithiumMatrix
8
«Если ответ сильно различается от языка к языку, то давайте сосредоточимся на c (++)». c и c ++ - это разные языки!
Johnsyweb
11
@zhermes: Комментарий о том, что C и C ++ являются разными языками, скрывает больше, чем вы думаете ... В C ++ вы скорее обнаружите, что используете преимущества объектов с автоматической продолжительностью хранения, следуйте идиоме RAII ... вы позволяете этим объектам заботиться о памяти управление для вас.
LihO

Ответы:

111

Нет. Операционные системы освобождают все ресурсы, удерживаемые процессами при их завершении.

Это относится ко всем ресурсам, которые поддерживает операционная система: память, открытые файлы, сетевые соединения, оконные дескрипторы ...

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

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

Joni
источник
5
Распространенной парадигмой в ОСРВ является однопроцессная модель с несколькими потоками и отсутствие защиты памяти между «задачами». Обычно куча одна. Именно так VxWorks работал раньше - и, вероятно, работает до сих пор.
marko
29
Обратите внимание, что не все ресурсы могут быть освобождены операционной системой. Сетевые подключения, транзакции с базой данных и т. Д., Если их не закрывать явно, могут возникнуть нежелательные результаты. Если не закрыть сетевое соединение, сервер может подумать, что вы все еще активны в течение неопределенного периода времени, а для серверов, ограничивающих количество активных подключений, это может случайно вызвать отказ в обслуживании. Незакрытие транзакций базы данных может привести к потере незафиксированных данных.
Ли Райан
1
@Marko: Последняя версия vxWorks теперь поддерживает RTP (процессы в реальном времени), которые поддерживают защиту памяти.
Xavier T.
20
«Операционные системы освобождают все ресурсы, удерживаемые процессами при их завершении». Не совсем так. Например, в (по крайней мере) Linux семафоры SysV и другие объекты IPC не очищаются при выходе из процесса. Вот почему ipcrmдля ручной очистки есть linux.die.net/man/8/ipcrm .
sleske
7
Кроме того, если у объекта есть временный файл, который он поддерживает, он явно не будет очищен впоследствии.
Mooing Duck
47

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

ouah
источник
20
Это более или менее связано с тем, что в стандарте C говорится о программах на C, а не об операционных системах, в которых C запускается ...
фонбранд
5
@vonbrand В стандарте C мог бы быть параграф, в котором говорится, что при mainвозврате вся память, выделенная им malloc, освобождается. Например, он говорит, что все открытые файлы закрываются до завершения программы. Для памяти, выделенной my malloc, она просто не указана. Теперь, конечно, мое предложение относительно ОС описывает то, что обычно делается, а не то, что предписывает Стандарт, поскольку в нем ничего не говорится об этом.
ouah
Позвольте мне исправить свой комментарий: стандарт говорит о C, а не о том, как программа запускается и останавливается. Вы вполне можете написать программу на C, работающую без ОС. В таком случае некому будет заниматься уборкой. Стандарт очень сознательно ничего не указывать , если не требуется, чтобы не ограничивают применения без необходимости.
vonbrand
2
@ouah: " когда главный возвращается ...". Это предположение. Мы должны учитывать « если основная прибыль ...». std::atexitтакже учитывает завершение программы через std::exit, а также есть std::abortи (специфично для C ++) std::terminate.
MSalters
@ouah: Если бы это было включено, atexitбыло бы невозможно использовать. :-)
R .. GitHub НЕ ПОМОГАЕТ ICE
28

Поскольку все ответы охватывают большинство аспектов вашего вопроса относительно современных ОС, но исторически есть один вопрос, о котором стоит упомянуть, если вы когда-либо программировали в мире DOS. Программы Terminant и Stay Resident (TSR) обычно возвращают управление системе, но находятся в памяти, которая может быть восстановлена ​​программным / аппаратным прерыванием. Было нормально видеть такие сообщения, как «недостаточно памяти! Попробуйте выгрузить некоторые из ваших TSR» при работе с этими ОС.

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

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

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

Abhijit
источник
Это действительно интересно, спасибо за историческую справку! Знаете ли вы, была ли эта парадигма результатом того, что освобождение памяти было слишком затратным с точки зрения вычислений, если в этом не было необходимости? Или альтернатива просто еще не была придумана?
DilithiumMatrix
1
@zhermes: Это было невозможно с вычислительной точки зрения, поскольку DOS просто не отслеживала выделение памяти для TSR. Во многом по определению: целью было остаться резидентом . Если вы хотите, чтобы ваш TSR освобождал некоторую, но не всю память, вам решать, что освобождать.
MSalters
2
@zhermes: DOS (как и CP / M, ее предок) не была тем, что вы бы назвали операционной системой в современном понимании. На самом деле это был просто набор утилит ввода-вывода, которые можно было вызывать стандартным способом в комплекте с командным процессором, который позволял запускать по одной программе за раз. Не было понятия о процессах, а память не была ни виртуальной, ни защищенной. TSR были полезным приемом, который мог сообщить системе, что они занимают до 64 КБ места, и подключаться к прерываниям, чтобы их вызывали.
Blrfl
8

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

Сказав это, память может быть не единственным, о чем вам нужно беспокоиться при работе с new / delete (вместо raw malloc / free). Память, выделенная в new, может быть освобождена, но того, что может быть сделано в деструкторах объектов, не произойдет. Возможно, деструктор какого-то класса при уничтожении записывает в файл контрольное значение. Если процесс просто завершается, дескриптор файла может быть сброшен, а память освобождена, но это контрольное значение не будет записано.

Мораль истории, всегда убирай за собой. Не позволяйте вещам болтаться. Не полагайтесь на то, что ОС очистит после вас. Убирайся за собой.

Андре Костур
источник
«Не полагайтесь на то, что ОС очистит после вас. Убирайся за собой ». Это часто бывает ... "очень, очень сложно" со сложными многопоточными приложениями. Фактические утечки, когда все ссылки на ресурс были потеряны, - это плохо. Разрешение ОС выполнять очистку вместо явного освобождения ссылок не всегда плохо и часто является единственным разумным решением.
Мартин Джеймс
1
В C ++ деструкторы будут вызываться при завершении программы (если не kill -9появится какой-нибудь не очень
умный
@vonbrand Верно, но если мы говорим об утечках с динамическими объектами, этих деструкторов не будет. Объект, выходящий за пределы области видимости, представляет собой необработанный указатель, а его деструктор не работает. (Конечно, см. Объекты RAII, чтобы смягчить эту проблему ...)
Андре Костур
1
Проблема с RAII в том, что он настаивает на освобождении объектов при выходе из процесса, от которых на самом деле не важно избавляться. С подключениями к БД вы должны быть осторожны, но общая память лучше очищается ОС (она делает гораздо лучше). Проблема проявляется в том, что программа, которая требует времени для выхода, когда объем выгружаемой памяти увеличивается. Это тоже нетривиально решить…
Donal Fellows
@vonbrand: Все не так просто. std::exitвызовет dtors, std::abortне будет , могут быть неперехваченные исключения.
MSalters
7

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

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

Джон
источник
Не могли бы вы испортить картину памяти ядра в случае упрощенной ОС? .. Типа этих операционных систем даже без многозадачности.
ulidtko
@ulidtko, это все испортит . Если моя программа время от времени требует, скажем, 1 ГиБ, и захватывает его на время, она запрещает использование этих ресурсов другим, даже если они не используются. Это может иметь значение сегодня или нет. Но окружающая среда будет меняться коренным образом . Гарантированный.
vonbrand
@vonbrand Редкое использование 1 Гбайт обычно не является проблемой (если у вас достаточно физической памяти), поскольку современные операционные системы могут выгружать неактивные биты. Проблема возникает, когда у вас больше активно используемой виртуальной памяти, чем физической памяти для ее размещения.
Donal Fellows,
5

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

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

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

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

Kaz
источник
5

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

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

С технической точки зрения, если ваши утечки имеют "сложность" памяти O (1), они в большинстве случаев в порядке, O (logn) уже неприятны (а в некоторых случаях фатальны) и O (N) + недопустимы.


источник
3

Общая память в POSIX-совместимых системах сохраняется до вызова shm_unlink или до перезагрузки системы.

klearn
источник
2

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

Чтобы привести пример, я однажды экспериментировал с печатью на PDF-принтер на Java, когда я завершил JVM в середине задания печати, процесс буферизации PDF-файлов оставался активным, и мне пришлось убить его в диспетчере задач, прежде чем я смог повторите попытку печати.

храповой урод
источник