Как free и malloc работают в C? - PullRequest
56 голосов
/ 24 декабря 2009

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

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

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

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

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

Ответы [ 8 ]

99 голосов
/ 24 декабря 2009

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

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

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

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

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

25 голосов
/ 24 декабря 2009

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

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

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

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

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

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

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

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

10 голосов
/ 24 декабря 2009

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

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

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

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

8 голосов
/ 24 декабря 2009

С http://opengroup.org/onlinepubs/007908775/xsh/free.html

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

7 голосов
/ 24 декабря 2009

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

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

5 голосов
/ 24 декабря 2009

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

2 голосов
/ 09 июня 2015

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

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

2 голосов
/ 17 февраля 2010

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

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

...