Что ДЕЙСТВИТЕЛЬНО происходит, когда вы не освобождаетесь после malloc?

438

Это то, что беспокоило меня целую вечность.

Мы все учимся в школе (по крайней мере, я был), что вы ДОЛЖНЫ освобождать каждый указатель, который выделяется. Мне немного любопытно, правда, о реальной стоимости освобождения памяти. В некоторых очевидных случаях, например, когда malloc вызывается внутри цикла или части выполнения потока, очень важно освободиться, чтобы не было утечек памяти. Но рассмотрим следующие два примера:

Во-первых, если у меня есть код, что-то вроде этого:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

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

Во-вторых, скажем, у меня есть программа, которая немного похожа на оболочку. Пользователи могут объявлять переменные типа aaa = 123, и они сохраняются в некоторой динамической структуре данных для последующего использования. Очевидно, что вам нужно использовать какое-то решение, которое вызовет некоторую функцию * alloc (hashmap, связанный список, что-то вроде этого). Для такого рода программ не имеет смысла когда-либо освобождаться после вызова malloc, поскольку эти переменные должны присутствовать всегда во время выполнения программы, и нет хорошего пути (который я вижу) для реализации этого со статически распределенным пространство. Это плохая конструкция, чтобы иметь кучу памяти, которая была выделена, но только освобождена как часть завершения процесса? Если да, то какая альтернатива?

  • 69
    +1, хорошая формулировка этого вопроса. Об этом часто спрашивают, но никогда не так кратко. Это удобная ссылка, когда появляются будущие вопросы того же типа.
  • 6
    @NTDLS Волшебство системы рейтингов, работающей на самом деле один раз: 6 лет спустя и «более достойный» ответ действительно поднялся на вершину.
Показать ещё 2 комментария
Теги:
malloc
free

17 ответов

291
Лучший ответ

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

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

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

С другой стороны, подобное напоминание о закрытии ваших файлов при выходе имеет гораздо более конкретный результат - если вы этого не сделаете, данные, которые вы им написали, могут не очиститься или если они временный файл, они могут не удаляться, когда вы закончите. Кроме того, дескрипторы базы данных должны иметь свои транзакции и затем закрываться, когда вы закончите с ними. Аналогично, если вы используете объектно-ориентированный язык, такой как С++ или Objective C, не освобождая объект, когда вы закончите с ним, это означает, что деструктор никогда не будет вызван, и любые ресурсы, которые несет класс, могут не очиститься.

  • 1
    +1 за упоминание других языков.
  • 13
    Вероятно, было бы также хорошо упомянуть, что не все используют современную операционную систему, если кто-то берет вашу программу (и она все еще работает в ОС, которая не восстанавливает память), запускает ее, а затем GG.
Показать ещё 18 комментариев
88

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

Источник: Распределение и мифы GC (оповещение PostScript!)

Миф о распределении 4: программы, не содержащие мусор должен всегда освобождать всю память они выделяют.

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

В большинстве случаев освобождение памяти перед выходом программы не имеет смысла.ОС все равно вернет его. Свободно будет касаться и печатать мертвых объекты; ОС не будет.

Следствие: будьте осторожны с "утечкой" детекторов ", которые подсчитывают распределения. Некоторые" утечки" хороши!

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

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

  • 10
    Получает ли этот ответ специальный значок для принятого ответа с отрицательным голосом?
  • 9
    Более конкретно - почему у него отрицательные голоса? Это не плохой ответ.
Показать ещё 7 комментариев
47

=== Как насчет будущей проверки и повторного использования кода? ===

Если вы не пишете код для освобождения объектов, то вы ограничиваете использование кода только безопасным, если вы можете зависеть от свободной памяти от закрытого процесса... т.е. небольшого разового использования проектов или проектов "выбросить" [1]), где вы знаете, когда процесс закончится.

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


[1] в отношении проектов "выброса".Код, используемый в проектах "Отбрасывание", имеет способ не выбрасываться.Следующее, что вы знаете, прошло десять лет, и ваш код "выброса" все еще используется).

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

  • 5
    Проголосовал за "небольшие проекты". Существует много крупных проектов, которые намеренно не освобождают память при выходе, потому что вы знаете, что знаете свои целевые платформы. ИМО, более точным примером были бы «изолированные проекты». Например, если вы создаете многократно используемую библиотеку, которая будет включена в другие приложения, нет четко определенной точки выхода, поэтому не должно быть утечки памяти. Для автономного приложения вы всегда будете точно знать, когда завершится процесс, и сможете принять осознанное решение выгрузить очистку в ОС (которая должна выполнять проверки в любом случае).
  • 0
    Вчерашнее приложение является сегодняшней библиотечной функцией, а завтра оно будет связано с долгоживущим сервером, который вызывает его тысячи раз.
Показать ещё 3 комментария
42

Вы правы, никакого вреда не сделано, и быстрее выйти из

Для этого существуют различные причины:

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

  • Почти все реализации free() никогда не возвращают память в операционную систему.

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

Что касается возможности повторного использования будущего кода, оправдывающего уверенность в бессмысленных операциях: это соображение, но это, возможно, не способ Agile. YAGNI!

  • 2
    Однажды я работал над проектом, в котором мы потратили немного времени, пытаясь понять, как используется память программ (мы были обязаны это поддерживать, мы не писали ее). Исходя из опыта, я анекдотически согласен с вашей второй пулей. Тем не менее, я хотел бы услышать, что вы (или кто-то другой) предоставили больше доказательств того, что это правда.
  • 3
    Не берите в голову, нашли ответ: stackoverflow.com/questions/1421491/… . Спасибо тебе!
Показать ещё 3 комментария
19

Я полностью не согласен со всеми, кто говорит, что ОР правильный или нет никакого вреда.

Все говорят о современной и/или устаревшей ОС.

Но что, если я в среде, где у меня просто нет ОС? Где нет ничего?

Представьте, что теперь вы используете прерывания с помощью стилей и выделяете память. В стандарте C ISO/IEC: 9899 - это срок службы памяти, обозначаемый как:

7.20.3 Функции управления памятью

1 Порядок и смежность хранилища, выделенные последовательными вызовами функций calloc, malloc и realloc, не определены. Указатель возвращается, если выделение успешно выполняется соответствующим образом, чтобы его можно было назначить указателю на любой тип объекта, а затем использовать для доступа к такому объекту или массиву таких объектов в выделенном пространстве (пока пространство явно не освобождено), Время жизни выделенного объекта простирается от выделения до освобождения. [...]

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

Итак, другими словами: не освобождение памяти - это не просто плохая практика. Он производит не переносимый, а не код соответствия. Который, по крайней мере, может рассматриваться как "правильный, если следующее: [...], поддерживается средой".

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

Поэтому, в общем случае, просто C (как обозначается OP), это просто создает ошибочный и не переносимый код.

  • 2
    Противоположным аргументом является то, что, если вы являетесь встроенной средой, вы - как разработчик - были бы гораздо более требовательны в управлении своей памятью. Обычно это делается для того, чтобы заранее заранее выделить статическую фиксированную память вместо того, чтобы вообще иметь какие-либо mallocs / reallocs во время выполнения.
  • 1
    @lunarplasma: Хотя то, что вы говорите, не является неправильным, это не меняет того факта, о котором говорится в стандарте языка, и каждый, кто действует против / далее, пусть даже по здравому смыслу, создает ограниченный код. Я могу понять, если кто-то скажет: «Мне не нужно заботиться об этом», поскольку достаточно случаев, когда это нормально. НО, что нужно хотя бы знать, ПОЧЕМУ ему не нужно заботиться. и особенно не опускайте его, пока вопрос не связан с этим частным случаем. А так как ОП задает вопрос о Си вообще по теоретическим (школьным) аспектам. Нельзя говорить «Тебе не нужно»!
Показать ещё 4 комментария
19

Я обычно освобождаю каждый выделенный блок, как только я уверен, что с ним все закончится. Сегодня моя точка входа в программу может быть main(int argc, char *argv[]), но завтра она может быть foo_entry_point(char **args, struct foo *f) и введена как указатель функции.

Итак, если это произойдет, у меня теперь есть утечка.

Что касается вашего второго вопроса, если моя программа взяла ввод как a = 5, я бы выделил место для a или перераспределял одно и то же пространство на следующем a = "foo". Это будет оставаться выделенным до:

  • Пользователь набрал 'unset a'
  • Была введена моя функция очистки, либо обслуживая сигнал, либо пользователь набрал "quit"

Я не могу думать о какой-либо современной ОС, которая не восстанавливает память после выхода процесса. Опять же, free() дешево, почему бы не убрать? Как говорили другие, такие инструменты, как valgrind, отлично подходят для обнаружения утечек, о которых вам действительно нужно беспокоиться. Несмотря на то, что блоки, на которые вы, например, будете помечены как "все еще доступные", просто лишний шум в выходе, когда вы пытаетесь обеспечить отсутствие утечек.

Другой миф: "Если его в main(), мне не нужно его освобождать", это неверно. Рассмотрим следующее:

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

Если это было до начала forking/daemonizing (и в теории работает вечно), ваша программа просто пропустила неопределенный размер в 255 раз.

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

На самом деле, будь добр к бедной душе, которая должна поддерживать ваши вещи, когда вы переходите к другим вещам.. передайте их им "valgrind clean":)

  • 1
    И да, у меня однажды был напарник, который сказал мне: «Мне никогда не нужно вызывать free () в main ()» <shudders>
  • 0
    free() is cheap если у вас нет миллиарда структур данных со сложными взаимосвязями, которые вы должны выпустить по одной, обход структуры данных в попытке освободить все может в конечном итоге значительно увеличить время выключения, особенно если половина этих данных структура уже выгружена на диск, без какой-либо выгоды.
Показать ещё 1 комментарий
12

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

Как утверждается, одна из причин, по которой люди все еще настаивают на том, что это хорошо, чтобы освободить все до выхода, является то, что отладчики памяти (например, valgrind on Linux) обнаруживают разблокированные блоки как утечки памяти, и если у вас также есть "настоящие" утечки памяти, становится сложнее определить их, если вы также получите "поддельные" результаты в конце.

  • 1
    Разве Valgrind не делает хорошую работу, различая «утечка» и «все еще достижимая»?
  • 11
    -1 для «совершенно нормально». Плохая практика кодирования - оставлять выделенную память без ее освобождения. Если этот код был извлечен в библиотеку, то это повлекло бы за собой утечки памяти повсюду.
Показать ещё 2 комментария
11

Этот код будет работать нормально, но рассмотрим проблему повторного использования кода.

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

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

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

  • 2
    Возможно, стоит отметить возможность того, что кто-то не только скопирует фрагмент, но и возможность того, что программа, которая была написана для выполнения какого-то определенного действия, однажды была изменена для его повторного выполнения. В таком случае было бы хорошо, если бы память выделялась один раз, а затем использовалась многократно, никогда не освобождаясь, но выделение и оставление памяти для каждого действия (без его освобождения) может иметь катастрофические последствия.
11

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

Изменить: он не на 100% точнее сказать, что другие запущенные программы лишены этой памяти. Операционная система всегда позволяет им использовать ее за счет замены вашей программы на виртуальную память (</handwaving>). Дело в том, что если ваша программа освобождает память, которую она не использует, смену виртуальной памяти с меньшей вероятностью необходимо.

5

Каков реальный результат здесь?

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

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

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

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

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

3

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

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

  • 1
    Я хочу сказать -1 для вашего первого утверждения «опасности нет», за исключением того, что вы затем даете вдумчивый ответ о том, почему существует опасность.
  • 2
    Поскольку опасности идут, это довольно мягко - я возьму утечку памяти по segfault в любой день.
Показать ещё 5 комментариев
2

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

Соответствующий раздел - "Забыть о свободной памяти" в главе API памяти на стр. 6, в которой дается следующее объяснение:

В некоторых случаях может показаться, что не вызов free() является разумным. Для Например, ваша программа недолговечна и скоро выйдет; в этом случае, когда процесс умирает, ОС очистит все выделенные страницы и таким образом, утечка памяти не будет происходить сама по себе. Хотя это, безусловно, "работает", (см. в сторону на стр. 7), вероятно, это плохая привычка развиваться, поэтому будьте осторожны выбора такой стратегии

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

За кулисами операционная система будет переводить "виртуальные адреса", которые пользователь видит в фактических адресах, указывающих на физическую память.

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


РЕДАКТИРОВАТЬ: Вышеупомянутый фрагмент, указанный в выдержке, копируется ниже.

ASIDE: ПОЧЕМУ НЕ УДАЛЕНА ПАМЯТЬ ПРОДОЛЖИТЕЛЬНОСТЬ ПРОЦЕССА

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

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

из Страница 7 из Глава 3. Интерфейс памяти

     

Операционные системы: три простых штуки
  Ремзи Х. Арпачи-Дуссо и Андреа К. Арпачи-Дуссо   Книги Арпачи-Дуссо   Март 2015 г. (версия 0.90)

2

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

2

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

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

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

2

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

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

В большинстве программ это не вариант, или это может привести к нехватке памяти.

0

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

-1

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню