The system that runs your program, together with the C library functions, keeps track of the memory chunks that you allocate and free again. This is called memory management.
Memory management keeps private data structures that we as C programmers don't have access to. These data structures record which chunks of memory are in use by your program. (It's common that part of this chunk administration is kept at addresses just outside (often below) the allocated chunk.)
When you free memory, the memory management system only updates its data structures to indicate that your chunk of memory is no longer in use. Freeing memory does not clear it. A good reason why freed memory is not cleared is program performance.
In many systems each program is being assigned a private continuous block of memory. The memory management system takes a chunk from this this block when you allocate and returns the chunk to the block when you free it.
This all means that the contents of the memory you freed remains intact until your program allocates a new chunk of memory that happens to overlap your earlier freed chunk and you start overwriting your earlier data.
When you allocate a new chunk it often won't overlap with a chunk you just freed. This is due to the fact that the memory management system has algorithms in place to for example make sure that the continuous block it manages is not fragmented. Memory fragmentation occurs when small chunks of allocated memory are all over the large block of managed memory. It's a bit similar to hard disk space fragmentation where you also have a continuous space that is used and freed again.