Свободно() обнуляет память?
До сегодняшнего дня я жил в убеждении, что вызов free()
на память освобождает его для дальнейшего размещения без каких-либо других модификаций. Особенно, учитывая этот SO вопрос, в котором четко указано, что free()
НЕ ОТПУСКАЕТ память.
Однако рассмотрим этот фрагмент кода (test.c):
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* pointer;
if (NULL == (pointer = malloc(sizeof(*pointer))))
return EXIT_FAILURE;
*pointer = 1337;
printf("Before free(): %p, %d\n", pointer, *pointer);
free(pointer);
printf("After free(): %p, %d\n", pointer, *pointer);
return EXIT_SUCCESS;
}
Компиляция (как GCC, так и Clang):
gcc test.c -o test_gcc
clang test.c -o test_clang
Результат:
$ ./test_gcc
Before free(): 0x719010, 1337
After free(): 0x719010, 0
$ ./test_clang
Before free: 0x19d2010, 1337
After free: 0x19d2010, 0
Почему так? Я все время жил во лжи или я неправильно понял некоторые основные понятия? Или есть лучшее объяснение?
Некоторая техническая информация:
Linux 4.0.1-1-ARCH x86_64
gcc version 4.9.2 20150304 (prerelease) (GCC)
clang version 3.6.0 (tags/RELEASE_360/final)
Ответы
Ответ 1
Нет однозначного ответа на ваш вопрос.
- Во-первых, внешнее поведение освобожденного блока будет зависеть от того, было ли оно выпущено в систему или сохранено как свободный блок во внутреннем пуле памяти процесса или в библиотеке времени выполнения C. В современных операционных системах память, "возвращенная в систему", станет недоступной для вашей программы, а это значит, что вопрос о том, был ли он обнулен или нет, является спорным.
(Остальное относится к блокам, сохраненным во внутреннем пуле памяти.)
-
Во-вторых, мало смысла в заполнении освобожденной памяти с какой-либо определенной ценностью (поскольку вы не должны ее получать), тогда как стоимость выполнения такой операции может быть значительной. Вот почему большинство реализаций ничего не делают для освобождения памяти.
-
В-третьих, на этапе отладки заполнение свободной памяти с некоторым предопределенным значением мусора может быть полезно при обнаружении ошибок (например, доступ к уже освобожденной памяти), поэтому многие отладочные реализации стандартной библиотеки заполняют свободную память с помощью некоторое заранее определенное значение или шаблон. (Zero, BTW, не лучший выбор для такого значения. Что-то вроде шаблона 0xDEADBABE
имеет гораздо больший смысл). Но опять же это делается только в отладочных версиях библиотеки, где влияние производительности не является проблемой.
-
В-четвертых, многие (большинство) популярных реализаций управления памятью кучи будут использовать часть освобожденного блока для своих внутренних целей, т.е. хранить там некоторые значимые значения. Это означает, что эта область блока изменяется на free
. Но в целом он не "обнулен".
И все это, конечно, сильно зависит от реализации.
В целом, ваше первоначальное убеждение совершенно корректно: в версии версии кода освобожденный блок памяти не подвергается никаким изменениям в масштабе всего блока.
Ответ 2
free()
не является нулевой памятью как общее правило. Он просто освобождает его для повторного использования в будущем вызове malloc()
. Некоторые реализации могут заполнять память известными значениями, но это просто деталь реализации библиотеки.
Время выполнения Microsoft отлично использует маркировку освобожденной и выделенной памяти с полезными значениями (см. В Visual Studio С++, какие представления распределения памяти? для получения дополнительной информации). Я также видел, что он заполнен значениями, которые при выполнении могут вызвать четко определенную ловушку.
Ответ 3
Есть ли лучшее объяснение?
Есть. Выделение указателя после того, как оно было free()
d, приводит к поведению undefined, поэтому реализация имеет разрешение делать все, что угодно, включая действие обмана, полагая, что область памяти заполнена нулями.
Ответ 4
Есть еще одна ошибка, которую вы, возможно, не знали, здесь:
free(pointer);
printf("After free(): %p \n", pointer);
Даже просто чтение значение pointer
после free
это поведение undefined, потому что указатель становится неопределенным.
Разумеется, разглашение освобожденного указателя - как в приведенном ниже примере - также недопустимо:
free(pointer);
printf("After free(): %p, %d\n", pointer, *pointer);
пс. В общем случае при печати адреса с %p
(например, в printf
) его следует передать в (void*)
, например. (void*)pointer
- в противном случае вы также получите поведение undefined
Ответ 5
Свободно() обнуляет память?
Нет. реализация glibc malloc может перезаписывать до четырех раз больше размера указателя бывших пользовательских данных для внутренних служебных данных.
Подробности:
Ниже приведена структура malloc_chunk
glibc (см. здесь):
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
Область памяти для пользовательских данных в выделенной ячейке памяти начинается после записи size
. После того, как free
называется пространством памяти, в котором пользовательские данные были использованы для списков свободных фрагментов памяти, поэтому первые байты 4 * sizeof(struct malloc_chunk *)
из бывших пользовательских данных, вероятно, перезаписаны, следовательно, другое значение, чем прежнее значение пользовательских данных печатается. Это поведение undefined. Если выделенный блок больше, может быть ошибка сегментации.
Ответ 6
Как указывали другие, вам не разрешено ничего делать с указателем free
d (иначе это опасное undefined поведение, который вы всегда должны избегать, см. this).
На практике я рекомендую никогда просто кодировать
free(ptr);
но всегда кодирование
free(ptr), ptr=NULL;
(так как практически это помогает много ловить некоторые ошибки, кроме double free
s)
Если ptr
не будет использоваться после этого, компилятор будет оптимизировать, пропустив назначение из NULL
На практике компилятор знает о free
и malloc
(поскольку стандартные заголовки библиотек C, вероятно, объявят эти стандартные функции с соответствующими атрибутами - понимается как GCC и Clang/LLVM), поэтому может быть оптимизирован код (в соответствии со стандартной спецификацией malloc
и free
....), но реализация malloc
и free
часто предоставляется вашим C стандартная библиотека (например, очень часто GNU glibc или musl-libc в Linux), поэтому фактическое поведение обеспечивается вашим libc
(а не самим компилятором). Прочтите соответствующую документацию, в частности бесплатную (3) страницу руководства.
Кстати, в Linux обе glibc
и musl-libc
являются свободным программным обеспечением, поэтому вы можете изучить их исходный код, чтобы понять их поведение. Они иногда получают виртуальную память из ядра с помощью системного вызова, такого как mmap (2) (и позже отпустите память в ядро, используя munmap (2)), но они обычно пытаются повторно использовать ранее free
d память для будущего malloc
s
На практике free
может munmap
ваша память (особенно для больших зон с памятью malloc
), а затем вы получите SIGSEGV
, если вы решите разыменовать (позже), что free
d, но часто (особенно для небольших зон памяти) он просто сможет повторно использовать эту зону позже. Точное поведение специфично для реализации. Обычно free
не очищает и не записывает только что освобожденную зону.
Вам даже разрешено переопределять (т.е. повторно реализовать) свои собственные malloc
и free
, возможно, связывая специальную библиотеку, такую как libtcmalloc, если ваша реализация имеет поведение, совместимое с тем, что говорит стандарт C99 или C11.
В Linux отключите перекомпоновку памяти и используйте valgrind. Скомпилируйте с gcc -Wall -Wextra
(и, возможно, -g
при отладке, вы можете также рассмотреть возможность передачи -fsanitize=address
в недавние gcc
или clang
, по крайней мере, для поиска некоторых непослушных ошибок.).
BTW, иногда Бумерский консервативный сборщик мусора мог бы быть полезен; вы будете использовать (в своей программе) GC_MALLOC
вместо malloc
, и вам не понадобится free
-имя памяти.
Ответ 7
free()
может фактически вернуть память в операционную систему и сделать процесс меньше. Обычно все, что он может сделать, это разрешить более поздний вызов malloc для повторного использования пробела. Тем временем пространство остается в вашей программе как часть свободного списка, используемого внутри malloc
.