Как работают malloc () и free ()?

243

Я хочу знать, как malloc и free работа.

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

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

  • 5
    Разве это не должно зависеть от компилятора и используемой библиотеки времени выполнения?
  • 9
    это будет зависеть от реализации CRT. Таким образом, вы не можете обобщить это.
Показать ещё 8 комментариев
Теги:
malloc
memory-management
free

14 ответов

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

ОК, некоторые ответы о malloc уже опубликованы.

Чем интереснее будет , тем лучше работает (и в этом направлении malloc тоже может быть понят лучше).

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

Так что возвращение 40 байт в ОС просто не сработает. Так что же делать бесплатно?

Free поместит блок памяти в свой собственный список свободных блоков. Обычно он также пытается объединить соседние блоки в адресном пространстве. Список свободных блоков - это всего лишь круговой список фрагментов памяти, которые в начале имеют некоторые административные данные. Это также является причиной того, что управление очень маленькими элементами памяти со стандартным malloc/free неэффективно. Каждый фрагмент памяти нуждается в дополнительных данных, а с меньшими размерами происходит более фрагментация.

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

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

Почему ваш код сбой:

Причина в том, что, написав 9 символов (не забывайте о завершающем нулевом байте) в область размером 4 символа, вы, вероятно, перезапишете административные данные, хранящиеся для другого фрагмента памяти, который находится "позади" вашего фрагмента данных (поскольку эти данные чаще всего хранятся "впереди" блоков памяти). Когда free затем пытается помещать ваш кусок в свободный список, он может коснуться этих административных данных и, следовательно, наткнуться на перезаписанный указатель. Это приведет к сбою системы.

Это довольно грациозное поведение. Я также видел ситуации, когда указатель бегства где-то перезаписывал данные в списке без памяти, и система сразу не сработала, но некоторые подпрограммы позже. Даже в системе средней сложности такие проблемы могут быть действительно сложными для отладки! В одном случае, в котором я участвовал, нам потребовалось несколько дней (большая группа разработчиков), чтобы найти причину сбоя, поскольку он находился в совершенно другом месте, чем тот, который указан дампом памяти. Это как бомба замедленного действия. Вы знаете, ваш следующий "свободный" или "malloc" сработает, но вы не знаете, почему!

Вот некоторые из худших проблем C/С++ и одна из причин, почему указатели могут быть настолько проблематичными.

  • 54
    Так много людей не понимают, что free () может не вернуть память в ОС, это бесит. Спасибо за помощь в их просвещении.
  • 0
    Артелий: наоборот, новая воля всегда есть?
Показать ещё 6 комментариев
50

Как говорит aluser в этой теме форума:

В вашем процессе есть область памяти, от адреса x до адреса y, называемый кучей. Все данные вашего malloc'd живут в этой области. таНос() хранит некоторую структуру данных, скажем, список, из всех свободных кусков пространство в куче. Когда вы вызываете malloc, он просматривает список для кусок, достаточно большой для вас, возвращает указатель на него, и записывает тот факт, что он не освобождает больше, а также насколько он большой. Когда вы вызываете free() с тем же указателем, free() просматривает, как большой этот кусок и добавляет его обратно в список свободных кусков(). если ты вызовите malloc(), и он не может найти достаточно большой кусок в куче, он использует сбщ. brk() для увеличения кучи, т.е. увеличивает адрес y и заставьте все адреса между старым y и новым y быть действительными Память. brk() должен быть syscall; нет способа сделать то же самое полностью из пользовательского пространства.

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

malloc() and free() don't work the same way on every O/S.

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

Одна реализация malloc/free делает следующее:

  • Получить блок памяти из ОС через sbrk() (вызов Unix).
  • Создайте заголовок и нижний колонтитул вокруг этого блока памяти с некоторой информацией, такой как размер, разрешения и где следующий и предыдущий блок.
  • Когда приходит вызов malloc, ссылается на список, который указывает на блоки соответствующего размера.
  • Затем этот блок возвращается, а верхние и нижние колонтитулы соответственно обновляются.
24

Защита памяти имеет гранулярность страницы и требует взаимодействия с ядром

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

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

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

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

Итак, ваш блок находится, сидя в свободном списке. Вы почти всегда можете получить доступ к нему и соседней памяти так же, как если бы он все еще был выделен. C компилируется прямо в машинный код и без специальных отладочных устройств нет проверок работоспособности и нагрузок. Теперь, если вы попытаетесь получить доступ к свободному блоку, поведение undefined по стандарту, чтобы не предъявлять необоснованных требований к разработчикам библиотек. Если вы попытаетесь получить доступ к свободной памяти или памяти вне выделенного блока, есть разные вещи, которые могут пойти не так:

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

Теория работы

Таким образом, работая назад от вашего примера до общей теории, malloc (3) получает память из ядра, когда ему это нужно, и обычно в единицах страниц. Эти страницы делятся или консолидируются по мере необходимости. Malloc и свободно сотрудничать, чтобы поддерживать каталог. По возможности, они объединяются со смежными свободными блоками, чтобы обеспечить большие блоки. Каталог может включать или не включать использование памяти в освобожденных блоках для формирования связанного списка. (Альтернатива - это немного больше разделяемой памяти и пейджинговой дружбы, и она предполагает выделение памяти специально для каталога.) Malloc и free имеют небольшую, если есть возможность обеспечить доступ к отдельным блокам, даже если специальный и дополнительный код отладки компилируются в программы.


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

  • 0
    Хороший ответ. Рекомендую документ «Динамическое распределение памяти: обзор и критический обзор» Уилсона и др. Для углубленного обзора внутренних механизмов, таких как поля заголовков и свободные списки, которые используются распределителями.
  • 0
    Отличный ответ, спасибо.
24

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

Если вы выделяете 4 байта, например, malloc дает указатель на 4 байта. Что вы можете не понимать, так это то, что память размером 8-12 байт перед вашими 4 байтами используется malloc для создания цепочки всей выделенной вами памяти. Когда вы звоните бесплатно, он берет ваш указатель, выполняет резервное копирование до того, где он находится, и работает над этим.

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

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: То, что я описал, является общей реализацией malloc, но отнюдь не единственно возможным.

19

Там примерная реализация malloc() и free() в книге (Kernighan and Ritchie) Язык программирования C"). Поскольку вы должны были спросить, вы его не читали - выходите, читайте и раскаиваетесь в своих греховных путях.: D

12

Ваша строка strcpy пытается сохранить 9 байтов, а не 8, из-за терминатора NUL. Он вызывает поведение undefined.

Звонок на бесплатный может или не может быть сбой. Память "после" 4 байта вашего выделения может быть использована для чего-то другого с помощью вашей реализации C или С++. Если он используется для чего-то другого, тогда начертание на всем протяжении этого приведет к тому, что "что-то другое" пойдет не так, но если оно не будет использоваться ни для чего другого, тогда вам может это удастся избежать. "Уйти от него" может показаться хорошим, но на самом деле это плохо, потому что это означает, что ваш код будет работать нормально, но в будущем вам не обойтись без него.

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

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

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

11

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

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

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

6

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

5

malloc и free зависят от реализации. Типичная реализация включает разделение доступной памяти в "свободный список" - связанный список доступных блоков памяти. Многие реализации искусственно делят его на небольшие и большие объекты. Свободные блоки начинаются с информации о том, насколько большой блок памяти и где находится следующий, и т.д.

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

4

Ну, это зависит от реализации распределителя памяти и ОС.

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

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

Это не простая проблема. Часть распределителя ОС полностью не контролируется. Я рекомендую вам прочитать что-то вроде Doug Lea Malloc (DLMalloc), чтобы понять, как будет работать довольно быстрый распределитель.

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

3

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

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

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

Трудно сказать, потому что фактическое поведение отличается между разными компиляторами/временем выполнения. Даже сборки отладки/выпуска имеют другое поведение. Отладочные сборки VS2005 вставляют маркеры между распределениями для обнаружения повреждения памяти, поэтому вместо сбоя он будет утверждать в free().

1

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

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