free returns the memory to the system. It is the partner operation to malloc. Everything block of memory that you allocate with malloc should be returned to the system by calling free. After you call free you are no longer allowed to access that memory.
It's generally considered wise to set the pointer to NULL after you have called free, at least in debug builds, so that you can be sure that an error will be raised if you later attempt to dereference the pointer by mistake.
So, why can you still access memory that has been freed? Well, you can't reliably do so. It just so happens that the implementation of most memory management systems mean that you can sometimes get away with such abuses. Many memory managers allocate large blocks of memory from the operating systems and then, in turn, allocate small sub-blocks to the application. When you call free, the allocator returns that block back to its pool of readily available memory, but does not necessarily give the memory back to the OS, since OS memory allocation routines are typically expensive. Hence accessing it may still appear to work, because the memory is still allocated in your process. It's just that its now owned by the memory manager rather than by your app. Something like that is happening to you here.
Of course, sometimes you won't get away with abuses like this, most likely once you have deployed your software onto your most important client's machine!