Как работают free и malloc в C?


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

char *ptr = (char*)malloc(10*sizeof(char));

for (char i=0 ; i<10 ; ++i)
{
    ptr[i] = i+10;
}
++ptr;
++ptr;
++ptr;
++ptr;
free(ptr);

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

Спасибо большое

8   54  

8 ответов:

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

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

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

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

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

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

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

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

Я опишу три способа сделать это.

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

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

  • реализация может получить некоторую информацию из адреса, а некоторые из карты. Распределитель ядра 4.3 BSD (называемый, я думаю, "McKusick-Karel allocator") делает распределение мощности двух для объектов размером меньше размера страницы и сохраняет только размер страницы, делая все распределения с данной страницы одного размера.

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

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

если ваш пример, когда вы выделяете 10 байт памяти, система фактически резервирует, скажем, 14. Первые 4 содержат объем запрошенных данных (10), а затем возвращаемое значение malloc - это указатель на первый байт неиспользуемых данных в 14 распределяемый.

когда вы называете free на этом указателе система будет искать 4 байта назад, чтобы знать, что она изначально выделила 14 байт, чтобы она знала, сколько освободить. Эта система не позволяет вам предоставлять объем данных бесплатно в качестве дополнительного параметра для free сам по себе.

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

от http://opengroup.org/onlinepubs/007908775/xsh/free.html

функция free () освобождает пространство, на которое указывает ptr, то есть делает его доступным для дальнейшего распределения. Если ptr является нулевым указателем, никаких действий не происходит. В противном случае, если аргумент не соответствует указателю, ранее возвращенному функцией calloc (), malloc (), realloc() или valloc (), или если пространство освобождается вызовом free() или realloc (), поведение не определено. Любое использование указателя, который ссылается на освобожденное пространство, вызывает неопределенное поведение.

это неопределенное поведение - не делай этого. Только free() указатели, полученные от malloc(), никогда не настраивайте их до этого.

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

вы освобождаете неправильный адрес. Изменяя значение ptr, вы меняете адрес. free не может знать, что он должен попытаться освободить блок, начиная с 4 байт назад. Держите исходный указатель нетронутым и свободным, что вместо манипулируемого. Как указывали другие, результаты того, что вы делаете, "неопределенны"... отсюда и необработанное исключение.

никогда не делайте этого.

вы освобождаете неправильный адрес. Изменяя значение ptr, вы меняете адрес. free не может знать, что он должен попытаться освободить блок, начиная с 4 байт назад. Держите исходный указатель нетронутым и свободным, что вместо манипулируемого. Как указывали другие, результаты того, что вы делаете, "неопределенны"... отсюда и необработанное исключение

взято из книги:понимание и использование указателей C

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