Is free() zeroing out memory?

2019-01-11 16:13发布

问题:

Until today I lived in belief that calling free() on memory space releases it for further allocation without any other modifications. Especially, considering this SO question that clearly states that free() DOESN'T zero out memory.

Yet, let's consider this piece of code (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;
}

Compiling (both GCC and Clang):

gcc test.c -o test_gcc
clang test.c -o test_clang

Result:

$ ./test_gcc 
Before free(): 0x719010, 1337
After free(): 0x719010, 0
$ ./test_clang
Before free: 0x19d2010, 1337
After free: 0x19d2010, 0

Why is it so? Was I living in a lie all this time or did I misunderstand some basic concepts? Or is there a better explanation?

Some technical info:

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:

There's no single definitive answer to your question.

  • Firstly, the external behavior of a freed block will depend on whether it was released to the system or stored as a free block in the internal memory pool of the process or C runtime library. In modern OSes the memory "returned to the system" will become inaccessible to your program, which means that the question of whether it was zeroed-out or not is moot.

(The rest applies to the blocks retained in the internal memory pool.)

  • Secondly, there's little sense in filling freed memory with any specific value (since you are not supposed to access it), while the performance cost of such operation might be considerable. Which is why most implementations don't do anything to freed memory.

  • Thirdly, at debugging stage filling freed memory with some pre-determined garbage value can be useful in catching errors (like access to already freed memory), which is why many debug implementations of standard library will fill freed memory with some pre-determined value or pattern. (Zero, BTW, is not the best choice for such value. Something like 0xDEADBABE pattern makes a lot more sense.) But again, this is only done in debug versions of the library, where performance impact is not an issue.

  • Fourthly, many (most) popular implementations of heap memory management will use a portion of the freed block for its internal purposes, i.e. store some meaningful values there. Which means that that area of the block is modified by free. But generally it is not "zeroed".

And all this is, of course, heavily implementation-dependent.

In general, your original belief is perfectly correct: in the release version of the code a freed memory block is not subjected to any block-wide modifications.



回答2:

free() does not zero memory as a general rule. It simply releases it for re-used by a future call to malloc(). Certain implementations may fill the memory with known values but that is purely an implementation detail of the library.

Microsoft's runtime makes good use of marking freed and allocated memory with useful values (see In Visual Studio C++, what are the memory allocation representations? for more information). I have also seen it filled with values that when executed would cause a well-defined trap.



回答3:

is there a better explanation?

There is. Dereferencing a pointer after it has been free()d results in undefined behavior, so the implementation has the permission to do anything it pleases, including the act of tricking you into believing that the memory region has been filled with zeroes.



回答4:

There is another pitfall you might have not known actually, here:

free(pointer);

printf("After free(): %p \n", pointer); 

Even just reading the value of pointer after you free it is undefined behaviour, because the pointer becomes indeterminate.

Of course dereferencing the freed pointer - like in below example - is also not allowed:

free(pointer);

printf("After free(): %p, %d\n", pointer, *pointer);

ps. In general when printing address with %p (like in printf) cast it to (void*), e.g. (void*)pointer - otherwise you get undefined behaviour also



回答5:

Is free() zeroing out memory?

No. The glibc malloc implementation may overwrite up to four times the size of a pointer of the former user data for internal housekeeping data.

The details:

The following is the malloc_chunk structure of glibc (see here):

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;
};

The memory region for user data in an allocated memory chunk begins after the size entry. After free is called the memory space where the user data has been may be used for lists of free memory chunks, so the first 4 * sizeof(struct malloc_chunk *) bytes of the former user data are probably overwritten, hence another value than the former user data value is printed out. It is undefined behaviour. If the allocated block is larger there could be a segmentation fault.



回答6:

As others pointed out, you are not allowed to do anything with a freed pointer (else that is the dreaded undefined behavior, which you should always avoid, see this).

In practice, I recommend never coding simply

free(ptr);

but always coding

free(ptr), ptr=NULL;

(since practically speaking this helps a lot to catch some bugs, except double frees)

If ptr is not used after that, the compiler would optimize by skipping the assignment from NULL

In practice, the compiler knows about free and malloc (because the standard C libraries headers would probably declare these standard functions with appropriate function attributes -understood by both GCC & Clang/LLVM) so might be able to optimize the code (according to the standard specification of malloc & free....), but the implementation of malloc and free is often provided by your C standard library (e.g. very often GNU glibc or musl-libc on Linux) so the actual behavior is provided by your libc (not the compiler itself). Read appropriate documentation, notably free(3) man page.

BTW, on Linux, both glibc and musl-libc are free software, so you might study their source code to understand their behavior. They would sometimes obtain virtual memory from the kernel using a system call like mmap(2) (and later release back the memory to the kernel using munmap(2)), but they generally try to reuse previously freed memory for future mallocs

In practice, free could munmap your memory (notably for big memory malloc-ated zones) - and then you'll get a SIGSEGV if you dare dereferencing (later) that freed pointer, but often (notably for small memory zones) it would simply manage to reuse that zone later. The exact behavior is implementation specific. Usually free does not clear or write the just freed zone.

You are even allowed to redefine (i.e. re-implement) your own malloc and free, perhaps by linking a special library such as libtcmalloc, provided your implementation has a behavior compatible with what the C99 or C11 standard says.

On Linux, disable memory overcommit and use valgrind. Compile with gcc -Wall -Wextra (and probably -g when debugging; you might consider also passing -fsanitize=address to recent gcc or clang at least to hunt some naughty bugs.).

BTW, sometimes Boehm's conservative garbage collector might be useful; you'll use (in your whole program) GC_MALLOC instead of malloc and you won't care about free-ing memory.



回答7:

free() can actually return memory to the operating system and make the process smaller. Usually, all it can do is allow a later call to malloc to reuse the space. In the meantime, the space remains in your program as part of a free-list used internally by malloc.