Как действительно освобождают и работа 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);

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

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

58
задан Vijay Mathew 24 December 2009 в 08:54
поделиться

7 ответов

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

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

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

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

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

104
ответ дан 24 November 2019 в 18:42
поделиться

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

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

.
7
ответ дан 24 November 2019 в 18:42
поделиться

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

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

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

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

.
10
ответ дан 24 November 2019 в 18:42
поделиться

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

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

8
ответ дан 24 November 2019 в 18:42
поделиться

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

.
5
ответ дан 24 November 2019 в 18:42
поделиться

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

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

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

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

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

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

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

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

.
25
ответ дан 24 November 2019 в 18:42
поделиться

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

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

2
ответ дан 24 November 2019 в 18:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: