views:

260

answers:

7

I've a question about the memory management in C (and GCC 4.3.3 under Debian GNU/Linux).

According to the C Programming Language Book by K&R, (chap. 7.8.5), when I free a pointer and then dereference it, is an error. But I've some doubts since I've noted that sometimes, as in the source I've pasted below, the compiler (?) seems to work according a well-defined principle.

I've a trivial program like this, that shows how to return an array dynamically allocated:

#include <stdio.h>
#include <stdlib.h>


int * ret_array(int n)
{
    int * arr = (int *) malloc(10 * sizeof(int));
    int i;
    for (i = 0; i < n; i++)
    {
        arr[i] = i*2;
    }
    printf("Address pointer in ret_array: %p\n", (void *) arr);
    return arr;
}

int * ret_oth_array(int n)
{
    int * arr = (int *) malloc(10 * sizeof(int));
    int i;
    for (i = 0; i < n; i++)
    {
        arr[i] = i+n;
    }
    printf("Address pointer in ret_oth_array: %p\n", (void *) arr);
    return arr;
}

int main(void)
{
    int *p = NULL;
    int *x = NULL;
    p = ret_array(5);
    x = ret_oth_array(6);

    printf("Address contained in p: %p\nValue of *p: %d\n", (void *) p, *p);

    free(x);
    free(p);
    printf("Memory freed.\n");
    printf("*(p+4) = %d\n", *(p+4));
    printf("*x = %d\n", *x);

    return 0;
}

If I try to compile it with some arguments: -ansi -Wall -pedantic-errors, it doesn't raises errors or warning. Not only; it also runs fine.

Address pointer in ret_array: 0x8269008
Address pointer in ret_oth_array: 0x8269038
Address contained in p: 0x8269008
Value of *p: 0
Memory freed.
*p+4 = 8
*x = 0

*(p+4) is 8 and *x is 0. Why does this happen? If *(p+4) is 8, shouldn't *x be 6, since the first element of the x-array is 6?

Another strange thing happens if I try to change the order of the calls to free. E.g.:

int main(int argc, char * argv[])
{
/* ... code ... */

    free(p);
    free(x);

    printf("Memory freed.\n");
    printf("*(p+4) = %d\n", *(p+4));
    printf("*x = %d\n", *x);

    return 0;
}

In fact in this case the output (on my machine) will be:

*p+4 = 8
*x = 142106624

Why does the x pointer is really "freed", while the p pointer is freed (I hope) "differently"? Ok, I know that after freeing memory I should make the pointers to point to NULL, but I was just curious :P

+13  A: 

It is undefined behaviour, so it is an error to deference freed pointer as strange things may (and will) happen.

free() doesn't change the value of the pointer so it keeps pointing to the heap in the process address space - that's why you don't get segfault, however it is not specified and in theory on some platforms you can get segfault when you try to dereference pointer immediately after freeing.

To prevent this it is a good habit to assign pointer to NULL after freeing so it will fail in predictable way - segfault.

Please note that on some OSes (HP-UX, may be some others as well) it is allowed to dereference NULL pointer, just to prevent segfault (and thus hiding problems). I find it rather stupid as it makes things much more difficult to diagnose, although I don't know the full story behind this.

qrdl
+2  A: 

Although the behavior you're seeing seems to be consistent, it is not guaranteed to be so. Unforeseen circumstances may causes this behavior to change (let alone the fact that this is completely implemetnatation dependent).

Specifically, in your example you free() the array and then get the old content when you access the array. If you'll have additional malloc() calls after the free() - chances are that the old contents will be lost.

Itay
Ok, but why *p has the same result even after freeing p, while *x is 0 or other random values? In this case, since I don't re-malloc memory for x, it should be 6, shouldn't it? If you're right, it should work as the stack: the heap-pointer (if it exists) first increases than decreases, if I understood what you meant...
Markon
+2  A: 

Even if the memory is freed, it is not necessarily reused for some other purpose. Old pointers to your process memory are still valid pointers (though to unallocated memory) so you do not get segmentation faults either.

laalto
+8  A: 

This is probably not the answer you are looking for, but I'll give it a try anyway:

Since you're playing with undefined behaviour that you should never depend on in any way, shape or form, what good does it do to know how exactly one given implementation handles that?

Since gcc is free to change that handling at any given time, between versions, architectures or depending on the position and brightness of the moon, there's no use whatsoever in knowing how it handles it right now. At least not to the developer that uses gcc.

Joachim Sauer
+3  A: 

Once you free the dynamic-memory variable, it is not yours. The memory manager is free to do what ever it sees better with that piece of memory you where pointing to. The compiler doesn't do anything as far as I know with the freed blocks of memory, because it is a function and not defined by the language. Even if it is defined by the languages, the compiler just inserts calls to the underlying OS functions.

Just wanna say, It is undefined by the language, So you have to check your OS and watch that piece of memory after freeing it. The behavior maybe random, because sometimes other programs ask for memory, sometimes not!

by the way, It is different on my machine, the value changes for both pointers.

AraK
+3  A: 

*(p+4) is 8 and *x is 0. Why does this happen? If *(p+4) is 8, shouldn't *x be 6, since the first element of the x-array is 6?

One possible explanation for this would be that printf("...%i..."...) might internally use malloc to allocate a temporary buffer for it's string interpolation. That would overwrite the contents of both arrays after the first output.

Generally, I would consider it an error if a program relies on the value of a pointer after it has been freed. I would even say that it's a very bad code smell if it keeps the value of a pointer after it has been freed (instead of letting it go out of scope or overwriting it with NULL). Even if it works under very special circumstances (single-threaded code with a specific heap manager).

Niki
+8  A: 

free() (and malloc()) are not from gcc. They come from the C library, which on Debian is usually glibc. So, what you are seeing is glibc's behavior, not gcc's (and would change with a different C library, or a different version of the C library).

I particular, after you use free() you are releasing the memory block malloc() gave you. It's not yours anymore. Since it is not supposed to be used anymore, the memory manager within glibc is free to do whatever it wants with the memory block, including using parts of it as its own memory structures (which is probably why you are seeing its contents change; they have been overwritten with bookkeeping information, probaly pointers to other blocks or counters of some sort).

There are other things that can happen; in particular, if the size of your allocation was large enough, glibc can ask the kernel for a separate memory block for it (with mmap() or similar calls), and release it back to the kernel during the free(). In that case, your program would crash. This can in theory also happen in some circunstances even with small allocations (glibc can grow/shrink the heap).

CesarB
Thank you for your detailed answer :)I'll read the manual of glibc.
Markon