Вызов бесплатного указателя дважды

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

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

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

Ответ 1

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

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

Указатель по-прежнему указывает на этот адрес памяти. На этом этапе такое же пространство в куче может быть возвращено другим вызовом malloc. Когда вы второй раз вызываете free, вы не освобождаете предыдущие данные, а новые данные, и это может быть не очень полезно для вашей программы;)

Ответ 2

Чтобы ответить на ваш первый вопрос,

Так почему же он не знает, был ли освобожден указатель, который он получает через free()?

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

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

Однако для целей отладки некоторые реализации библиотек могут выполнять эту работу для вас, например DUMA или dmalloc, и последнее, но не менее важное, средство memcheck от Valgrind.

Теперь, технически, стандарт C не указывает никакого поведения, если вы вызываете free() по уже свободному указателю. Это undefined поведение.

C11, глава §7.22.3.3, free() функция

[...] если аргумент не соответствует указателю, ранее возвращенному управлением памятью или если пространство было освобождено вызовом free() или realloc(), поведение undefined.

Ответ 3

Когда вы вызываете malloc, вы получаете указатель. Библиотека времени выполнения должна отслеживать память malloc ed. Обычно malloc не сохраняет структуры управления памятью, отделенные от malloc ed памяти, но в одном месте. Таким образом, malloc для x байтов фактически принимает x + n байтов, где один возможный макет состоит в том, что первые n байтов содержат связанную структуру списка с указателями на следующий (и, возможно, предыдущий) выделенный блок памяти.

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

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

Установка указателя free d на NULL является хорошей практикой, потому что она помогает отлаживать. Если вы заходите в free d память, ваша программа может потерпеть крах, но она также может просто прочитать подозрительные значения и, возможно, потерпеть крах позже. Поиск основной причины может быть затруднен. Если вы установите free d-указатели на NULL, ваша программа немедленно сработает, когда вы попытаетесь получить доступ к памяти. Это очень помогает при отладке.

Ответ 4

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

Освобождение одного и того же куска дважды

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

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