Подтвердить что ты не робот

Свободно() обнуляет память?

До сегодняшнего дня я жил в убеждении, что вызов 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)
4b9b3361

Ответ 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.