it-swarm.com.ru

Как 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, чтобы я знал не только, как его использовать, но и мог понимать странные ошибки и исключения и лучше отлаживать мой код code

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

56
user238082

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

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

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

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

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

99
Jason Williams

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

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

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

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

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

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

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

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

25
DigitalRoss

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

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

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

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

10
Zeograd

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

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

8
PetrosB

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

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

7
sharptooth

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

5
Jason D

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

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

2
Jeet

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

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

2
Koray Tugay